diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0118f44..d6bf11e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.11.0
+
+This version adds support for Metadata and iOS deep links, and expands Tags support when updating or ending Live Activities.
+
## 1.10.0
### New Features
diff --git a/README.md b/README.md
index b09e63d..c4eb458 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,19 @@
-# ActivitySmith Python Library
+# ActivitySmith Python SDK
-The ActivitySmith Python library provides convenient access to the ActivitySmith API from Python applications.
-
-## Documentation
-
-See the [API reference](https://activitysmith.com/docs/api-reference/introduction).
-
-## Table of Contents
-
-- [Installation](#installation)
-- [Setup](#setup)
-- [Push Notifications](#push-notifications)
- - [Send a Push Notification](#send-a-push-notification)
- - [Rich Push Notifications with Media](#rich-push-notifications-with-media)
- - [Actionable Push Notifications](#actionable-push-notifications)
-- [Live Activities](#live-activities)
- - [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)
-- [Widgets](#widgets)
-- [App Icon Badge Count](#app-icon-badge-count)
-- [Channels](#channels)
-- [Tags](#tags)
+[Documentation](https://activitysmith.com/docs/sdks/python)
## Installation
-This package is available on PyPI:
+Install the ActivitySmith Python SDK with pip:
-```sh
+```bash
pip install activitysmith
```
-Alternatively, install from source with:
+## Quickstart
-```sh
-python -m pip install .
-```
-
-## Setup
+1. [Create an API key](https://activitysmith.com/app/keys)
+2. Set `ACTIVITYSMITH_API_KEY` or pass it directly to `ActivitySmith`.
```python
import os
@@ -52,18 +26,16 @@ from activitysmith import (
metric,
)
-activitysmith = ActivitySmith(
- api_key=os.environ["ACTIVITYSMITH_API_KEY"],
-)
+activitysmith = ActivitySmith(api_key=os.environ["ACTIVITYSMITH_API_KEY"])
```
## Push Notifications
### Send a Push Notification
-
-
-
+Send an immediate notification for a completed task or event.
+
+
```python
activitysmith.notifications.send(
@@ -74,24 +46,19 @@ activitysmith.notifications.send(
### Rich Push Notifications with Media
-
-
-
+
```python
activitysmith.notifications.send(
title="Homepage ready",
message="Your agent finished the redesign.",
media="https://cdn.example.com/output/homepage-v2.png",
- redirection="https://github.com/acme/web/pull/482",
)
```
-Send images, videos, or audio with your push notifications, press and hold to preview media directly from the notification, then tap through to open the linked content.
+Attach images, videos, or audio to your Push Notifications. Press and hold the notification to preview the media.
-
-
-
+
What will work:
@@ -100,23 +67,51 @@ What will work:
- direct video file URL: `.mp4`, `.mov`, etc.
- URL that responds with a proper media `Content-Type`, even if the path has no extension
+`media` cannot be combined with `actions`.
+
+### Push Notifications with Redirection
+
+Open a web page, run an iOS Shortcut, or open an app when someone taps the notification. `redirection` supports:
+
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+- **App deep links:** Installed apps or specific content within them
+ - **Spotify:** A track, e.g. `spotify:track:6rqhFgbbKwnb9MLmUQDhG6`
+ - **Termius:** `termius://` to open the app
+ - **Claude:** `claude://code` to open the Code tab
+ - **ChatGPT:** `chatgpt://` to open the app
+
+```python
+activitysmith.notifications.send(
+ title="Homepage ready",
+ message="Your agent finished the redesign.",
+ redirection="https://github.com/acme/web/pull/482",
+)
+```
+
### Actionable Push Notifications
-
-
-
+
+
+`open_url` actions open a web page, run an iOS Shortcut, or open an app when someone taps the button. Supported links:
-Push notification `redirection` and `actions` are optional. Use them to open HTTPS URLs, run a specific iPhone Shortcut with `shortcuts://run-shortcut?name=...`, or trigger backend webhook workflows.
-Webhooks are executed by the ActivitySmith backend.
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+- **App deep links:** Installed apps or specific content within them
+ - **Spotify:** A track, e.g. `spotify:track:6rqhFgbbKwnb9MLmUQDhG6`
+ - **Termius:** `termius://` to open the app
+ - **Claude:** `claude://code` to open the Code tab
+ - **ChatGPT:** `chatgpt://` to open the app
+
+Webhooks are executed by the ActivitySmith backend and must use HTTPS.
```python
activitysmith.notifications.send(
title="New subscription 💸",
message="Customer upgraded to Pro plan",
- redirection="https://crm.example.com/customers/cus_9f3a1d", # Optional
- actions=[ # Optional (max 4)
+ actions=[
action(
- title="Open CRM Profile",
+ title="Open CRM",
type="open_url",
url="https://crm.example.com/customers/cus_9f3a1d",
),
@@ -141,14 +136,31 @@ activitysmith.notifications.send(
## Live Activities
-There are six types of Live Activities:
+Choose the Live Activity type that matches what you want to show:
+
+
+
+**Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.
-- `stats`: best for showing business numbers side by side, such as revenue, sales, new users, conversion, refunds, or any other value you want visible at a glance
-- `metrics`: best for live percentage values that change often, like server CPU, memory usage, disk usage, or error rate
-- `segmented_progress`: best for anything that moves through clear stages, like deployments, onboarding flows, backups, ETL pipelines, migrations, and AI agent runs
-- `progress`: best for tracking real-time progress with percentage, like tasks, backups, migrations, syncs, or uploads
-- `alert`: best for status updates, such as feature adoption, reactivation, onboarding blockers, incidents, escalations, and other operational states
-- `timer`: best for countdowns and elapsed runtime, like benchmark runs, uploads, backups, transcodes, and long-running jobs
+
+
+**Metrics**: Track two related values with segmented bars, such as CPU and memory.
+
+
+
+**Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.
+
+
+
+**Progress**: Show percentage progress for jobs that move continuously toward completion.
+
+
+
+**Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.
+
+
+
+**Timer**: Count down from a duration, or count up from 00:00 while a job runs.
### Start & Update Live Activity
@@ -156,13 +168,7 @@ Use a stable `stream_key` to identify the metric, job, deployment, or system you
#### Stats
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -185,13 +191,7 @@ activitysmith.live_activities.stream(
#### Metrics
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -210,13 +210,7 @@ activitysmith.live_activities.stream(
#### Segmented Progress
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -233,13 +227,7 @@ activitysmith.live_activities.stream(
#### Progress
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -255,13 +243,7 @@ activitysmith.live_activities.stream(
#### Alert
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -278,13 +260,7 @@ activitysmith.live_activities.stream(
#### Timer
-
-
-
+
```python
activitysmith.live_activities.stream(
@@ -301,11 +277,11 @@ activitysmith.live_activities.stream(
For a countdown, send `duration_seconds`. You can update `title`, `subtitle`, `color`, or any other visible field as the work changes. Leave `duration_seconds` out unless you want to change the timer.
-To start at 00:00 and count up, set `counts_down: false` and leave out `duration_seconds`.
+To start at 00:00 and count up, set `counts_down=False` and leave out `duration_seconds`.
### 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.
+Call `end_stream(...)` with the same `stream_key` to dismiss the Live Activity. You can include final values before it is removed. Set `auto_dismiss_seconds` to dismiss it after a delay in seconds, or `auto_dismiss_minutes` for minutes. Use `0` for immediate dismissal. Seconds take precedence if both are set.
```python
activitysmith.live_activities.end_stream(
@@ -318,29 +294,84 @@ activitysmith.live_activities.end_stream(
metric(label="CPU", value=7, unit="%"),
metric(label="MEM", value=38, unit="%"),
],
- auto_dismiss_minutes=2,
+ auto_dismiss_seconds=30,
+ ),
+)
+```
+
+### Icons and Badges
+
+Add more context to Live Activities with icons and badges.
+
+#### Icon
+
+Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, `alert`, and `timer`.
+
+
+
+```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.
+
+
+
+```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`
+
### Live Activity Action
-Live Activities can include an action button.
+
-- `open_url`: open an HTTPS URL.
-- `open_url` with a `shortcuts://` URL: run an Apple Shortcut, for example to open an app.
-- `webhook`: trigger a backend GET/POST workflow.
+Live Activities can include an action button.
-
-
-
+- `open_url`: Open a web page or run an iOS Shortcut
+- `webhook`: Trigger a backend GET/POST workflow
#### Open URL action
+Open a web page or run an iOS Shortcut when someone taps the button. Supported links:
+
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+
```python
activitysmith.live_activities.stream(
"prod-web-1",
@@ -356,7 +387,7 @@ activitysmith.live_activities.stream(
action=action(
title="Dashboard",
type="open_url",
- url="https://ops.example.com/servers/prod-web-1",
+ url="https://status.example.com/servers/prod-web-1",
),
)
```
@@ -365,13 +396,15 @@ activitysmith.live_activities.stream(
```python
activitysmith.live_activities.stream(
- "deploy-payments-api",
+ "prod-web-1",
content_state=content_state(
- title="Deploying payments-api",
- subtitle="Running database migrations",
- type="segmented_progress",
- number_of_steps=5,
- current_step=3,
+ title="Server Health",
+ subtitle="prod-web-1",
+ type="metrics",
+ metrics=[
+ metric(label="CPU", value=76, unit="%"),
+ metric(label="MEM", value=52, unit="%"),
+ ],
),
action=action(
title="Chat with Jarvis",
@@ -408,13 +441,7 @@ activitysmith.live_activities.stream(
#### Secondary action
-
-
-
+
Use `secondary_action` when you want a second button beside the primary `action`.
@@ -454,78 +481,15 @@ activitysmith.live_activities.stream(
)
```
-### Icons and Badges
+## Lock Screen Widgets
-Add more context to Live Activities with icons and badges.
+
-#### Icon
+ActivitySmith lets you display any value on your Lock Screen with widgets - SaaS metrics, revenue, signups, uptime, habits, or anything else you want to track. Create a metric in the [web app](https://activitysmith.com/app/widgets), then update the metric value using our API, add a widget to your lock screen and it will fetch the latest update automatically.
-Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, `alert`, and `timer`.
+
-
-
-
-
-```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.
-
-
-
-
-
-```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`
-
-## Widgets
-
-
-
-
-
-ActivitySmith lets you display any value on your Lock Screen with widgets - SaaS metrics, revenue, signups, uptime, habits, or anything else you want to track. Create a metric in the web app, then update the metric value using our API, add a widget to your lock screen and it will fetch the latest update automatically.
-
-
-
-
+Use the metric key to update its value.
```python
activitysmith.metrics.update("deploy.success_rate", 99.9)
@@ -539,87 +503,108 @@ activitysmith.metrics.update("prod.status", "healthy")
## App Icon Badge Count
-
-
-
+
Show the number you care about on your ActivitySmith app icon. Track MRR, a customer count, a stock price, or any other value you want to keep in view.
-Set or update the badge value.
+### Set or update the badge value
```python
activitysmith.badge_count(8333)
```
-To clear the badge, set its value to 0.
+### Clear the badge
+
+Pass `0` to clear the badge.
```python
activitysmith.badge_count(0)
```
-## Channels
-
-Use `channels` to target specific team members or devices
+## Metadata
-### Push Notifications
+Metadata adds extra information to Push Notification and Live Activity details in ActivitySmith. It does not appear in the notification or Live Activity on your device.
```python
activitysmith.notifications.send(
title="New subscription 💸",
message="Customer upgraded to Pro plan",
- channels=["sales", "customer-success"],
+ metadata={
+ "customer_id": "382",
+ "plan": "Pro",
+ "amount": 29,
+ "trial": False,
+ },
+)
+
+activitysmith.live_activities.stream(
+ "customer-import",
+ title="Customer Import",
+ type="progress",
+ percentage=60,
+ metadata={
+ "job_id": "import-382",
+ "records": 1200,
+ },
)
```
-### Live Activities
+Values can be strings, numbers, or booleans. Metadata supports up to 50 entries and 16 KB of JSON, with keys up to 100 characters and strings up to 4,000 characters. Nested objects, arrays, and null values are not supported.
+
+## Tags
+
+Use Tags to organize and filter Push Notification and Live Activity history. Tags are created automatically when you first use them. Sending Tags requires SDK version 1.10.0 or later.
```python
-activitysmith.live_activities.start(
- content_state=content_state(
- title="Nightly Database Backup",
- subtitle="verify restore",
- type="progress",
- percentage=62,
- ),
- channels=["sales", "customer-success"],
+activitysmith.notifications.send(
+ title="New subscription 💸",
+ message="Customer upgraded to Pro plan",
+ tags=["user:382", "billing"],
)
```
-### App Icon Badge Count
+On Live Activity stream updates and legacy `update` or `end` calls, omit `tags` to keep existing Tags, supply a list to replace them, or pass `tags=[]` to clear them.
```python
-activitysmith.badge_count(3, channels=["sales", "customer-success"])
+activitysmith.live_activities.update(
+ activity_id="YOUR_ACTIVITY_ID",
+ title="Customer Import",
+ percentage=60,
+ tags=[],
+)
```
-## Tags
+## Channels
-Use `tags` to organize and filter your Push Notification and Live Activity history. Tags are created automatically when you first use them.
+Use `channels` to target specific team members or devices when sending Push Notifications, Live Activities, or App Icon Badge Count updates. Omit it for account-wide delivery.
```python
activitysmith.notifications.send(
title="New subscription 💸",
message="Customer upgraded to Pro plan",
- tags=["user:382", "billing"],
+ channels=["sales", "customer-success"],
)
-```
-## Error Handling
+activitysmith.live_activities.stream(
+ "nightly-backup",
+ content_state=content_state(
+ title="Nightly database backup",
+ type="segmented_progress",
+ number_of_steps=3,
+ current_step=1,
+ ),
+ channels=["ios-builds"],
+)
-```python
-try:
- activitysmith.notifications.send(
- title="New subscription 💸",
- )
-except Exception as err:
- print("Request failed:", err)
+activitysmith.badge_count(3, channels=["sales", "customer-success"])
```
-Request/response models are included and can be imported from `activitysmith_openapi.models`.
+## Error Handling
-## Requirements
+Wrap API calls with `try/except`. The SDK raises exceptions for non-2xx responses. Rate limit errors use the `error` and `message` fields, and Live Activity limit errors include `limit` and `active`. See [Rate Limits](https://activitysmith.com/docs/rate-limits) for details.
-- Python 3.9 or newer
+## Additional Resources
-## License
+### [PyPI Package](https://pypi.org/project/activitysmith/)
-MIT
+Install the ActivitySmith Python SDK from PyPI
diff --git a/activitysmith/client.py b/activitysmith/client.py
index 2cf4d0a..8493c3e 100644
--- a/activitysmith/client.py
+++ b/activitysmith/client.py
@@ -1,4 +1,5 @@
from __future__ import annotations
+from .normalization import normalize_metadata_request
from dataclasses import dataclass
from typing import Any
@@ -11,7 +12,11 @@
from activitysmith_openapi.api.push_notifications_api import PushNotificationsApi
from activitysmith_openapi.api.app_icon_badges_api import AppIconBadgesApi
-SDK_VERSION = "1.10.0"
+from activitysmith_openapi.models.metric_value_update_request import MetricValueUpdateRequest
+
+from .normalization import normalize_live_activity_request, normalize_metric_request
+
+SDK_VERSION = "1.11.0"
SDK_HEADER_NAME = "X-ActivitySmith-SDK"
SDK_HEADER_VALUE = f"python-v{SDK_VERSION}"
@@ -46,6 +51,11 @@ def _validate_push_request(request: Any) -> Any:
def _metric_value_request(value_or_request: Any, timestamp: Any | None = None) -> Any:
+ if isinstance(value_or_request, MetricValueUpdateRequest):
+ if timestamp is None:
+ return value_or_request
+ return value_or_request.model_copy(update={"timestamp": timestamp})
+
if isinstance(value_or_request, dict) and "value" in value_or_request:
if timestamp is None:
return value_or_request
@@ -211,6 +221,7 @@ def _build_push_request(
target: Any | None = None,
channels: Any | None = None,
tags: Any | None = None,
+ metadata: Any | None = None,
) -> Any:
request_fields = _compact_dict(
{
@@ -223,6 +234,7 @@ def _build_push_request(
"target": target,
"channels": channels,
"tags": tags,
+ "metadata": metadata,
}
)
@@ -284,6 +296,7 @@ def _build_live_activity_request(
target: Any | None = None,
channels: Any | None = None,
tags: Any | None = None,
+ metadata: Any | None = None,
) -> Any:
content_state_fields = _compact_dict(
{
@@ -317,11 +330,12 @@ def _build_live_activity_request(
"target": target,
"channels": channels,
"tags": tags,
+ "metadata": metadata,
}
)
if content_state is None and not content_state_fields and not request_fields:
- return request
+ return normalize_live_activity_request(request)
if request is None:
normalized: dict[str, Any] = {}
@@ -355,7 +369,7 @@ def _build_live_activity_request(
)
normalized.update(request_fields)
- return normalized
+ return normalize_live_activity_request(normalized)
class NotificationsResource:
@@ -375,6 +389,7 @@ def send(
target: Any | None = None,
channels: Any | None = None,
tags: Any | None = None,
+ metadata: Any | None = None,
):
request = _build_push_request(
request,
@@ -387,8 +402,9 @@ def send(
target=target,
channels=channels,
tags=tags,
+ metadata=metadata,
)
- normalized = _validate_push_request(_normalize_channels_target(request))
+ normalized = normalize_metadata_request(_validate_push_request(_normalize_channels_target(request)))
return self._api.send_push_notification(
push_notification_request=normalized
)
@@ -462,6 +478,7 @@ def start(
target: Any | None = None,
channels: Any | None = None,
tags: Any | None = None,
+ metadata: Any | None = None,
):
request = _build_live_activity_request(
request,
@@ -488,6 +505,7 @@ def start(
target=target,
channels=channels,
tags=tags,
+ metadata=metadata,
)
return self._api.start_live_activity(
live_activity_start_request=_normalize_channels_target(request)
@@ -517,6 +535,8 @@ def update(
step_color: Any | None = None,
action: Any | None = None,
secondary_action: Any | None = None,
+ tags: Any | None = None,
+ metadata: Any | None = None,
):
request = _build_live_activity_request(
request,
@@ -540,6 +560,8 @@ def update(
step_color=step_color,
action=action,
secondary_action=secondary_action,
+ tags=tags,
+ metadata=metadata,
)
return self._api.update_live_activity(live_activity_update_request=request)
@@ -568,6 +590,8 @@ def end(
auto_dismiss_minutes: Any | None = None,
action: Any | None = None,
secondary_action: Any | None = None,
+ tags: Any | None = None,
+ metadata: Any | None = None,
):
request = _build_live_activity_request(
request,
@@ -592,6 +616,8 @@ def end(
auto_dismiss_minutes=auto_dismiss_minutes,
action=action,
secondary_action=secondary_action,
+ tags=tags,
+ metadata=metadata,
)
return self._api.end_live_activity(live_activity_end_request=request)
@@ -623,6 +649,7 @@ def stream(
target: Any | None = None,
channels: Any | None = None,
tags: Any | None = None,
+ metadata: Any | None = None,
):
request = _build_live_activity_request(
request,
@@ -649,6 +676,7 @@ def stream(
target=target,
channels=channels,
tags=tags,
+ metadata=metadata,
)
return self._api.reconcile_live_activity_stream(
stream_key=stream_key,
@@ -681,6 +709,8 @@ def end_stream(
action: Any | None = None,
secondary_action: Any | None = None,
alert: Any | None = None,
+ tags: list[str] | None = None,
+ metadata: dict[str, Any] | None = None,
):
request = _build_live_activity_request(
request,
@@ -705,6 +735,8 @@ def end_stream(
action=action,
secondary_action=secondary_action,
alert=alert,
+ tags=tags,
+ metadata=metadata,
)
return self._api.end_live_activity_stream(
stream_key=stream_key,
@@ -745,14 +777,16 @@ def __init__(self, api: MetricsApi) -> None:
def update(self, key: str, value_or_request: Any, timestamp: Any | None = None):
return self._api.update_metric_value(
key=key,
- metric_value_update_request=_metric_value_request(value_or_request, timestamp),
+ metric_value_update_request=normalize_metric_request(
+ _metric_value_request(value_or_request, timestamp)
+ ),
)
# Backward-compatible generated-style alias.
def update_metric_value(self, key: str, metric_value_update_request: Any):
return self._api.update_metric_value(
key=key,
- metric_value_update_request=metric_value_update_request,
+ metric_value_update_request=normalize_metric_request(metric_value_update_request),
)
diff --git a/activitysmith/normalization.py b/activitysmith/normalization.py
new file mode 100644
index 0000000..2e4ead7
--- /dev/null
+++ b/activitysmith/normalization.py
@@ -0,0 +1,51 @@
+"""Adapt plain values to the generated client's union models."""
+from typing import Any
+
+from activitysmith_openapi.models.activity_metric_value import ActivityMetricValue
+from activitysmith_openapi.models.metric_value_update_request_value import MetricValueUpdateRequestValue
+
+
+def _wrap_value(value: Any, model: Any) -> Any:
+ if isinstance(value, model):
+ return value
+ if isinstance(value, bool) or not isinstance(value, (int, float, str, dict)):
+ raise ValueError("ActivitySmith: metric value must be a number or string")
+ if isinstance(value, dict):
+ if "actual_instance" not in value:
+ raise ValueError("ActivitySmith: metric value must be a number or string")
+ return model.model_validate(value)
+ return model(value)
+
+
+def normalize_live_activity_request(request: Any) -> Any:
+ request = normalize_metadata_request(request)
+ if not isinstance(request, dict):
+ return request
+ state = request.get("content_state")
+ if not isinstance(state, dict) or not isinstance(state.get("metrics"), (list, tuple)):
+ return request
+ metrics = []
+ for metric in state["metrics"]:
+ if isinstance(metric, dict) and "value" in metric:
+ metric = {**metric, "value": _wrap_value(metric["value"], ActivityMetricValue)}
+ metrics.append(metric)
+ return {**request, "content_state": {**state, "metrics": metrics}}
+
+
+def normalize_metric_request(request: Any) -> Any:
+ if isinstance(request, dict) and "value" in request:
+ return {**request, "value": _wrap_value(request["value"], MetricValueUpdateRequestValue)}
+ return request
+
+
+def normalize_metadata_request(request: Any) -> Any:
+ if not isinstance(request, dict) or "metadata" not in request:
+ return request
+ from activitysmith_openapi.models.metadata_value import MetadataValue
+ metadata = request["metadata"]
+ if not isinstance(metadata, dict):
+ raise ValueError("ActivitySmith: metadata must be an object")
+ return {**request, "metadata": {
+ key: value if isinstance(value, MetadataValue) else MetadataValue(value)
+ for key, value in metadata.items()
+ }}
diff --git a/activitysmith_openapi/__init__.py b/activitysmith_openapi/__init__.py
index 6c01606..f564b29 100644
--- a/activitysmith_openapi/__init__.py
+++ b/activitysmith_openapi/__init__.py
@@ -14,7 +14,7 @@
""" # noqa: E501
-__version__ = "1.10.0"
+__version__ = "1.11.0"
# import apis into sdk package
from activitysmith_openapi.api.app_icon_badges_api import AppIconBadgesApi
@@ -37,6 +37,7 @@
from activitysmith_openapi.models.activity_metric import ActivityMetric
from activitysmith_openapi.models.activity_metric_value import ActivityMetricValue
from activitysmith_openapi.models.alert_payload import AlertPayload
+from activitysmith_openapi.models.app_icon_badge_count_update_error import AppIconBadgeCountUpdateError
from activitysmith_openapi.models.app_icon_badge_count_update_request import AppIconBadgeCountUpdateRequest
from activitysmith_openapi.models.app_icon_badge_count_update_response import AppIconBadgeCountUpdateResponse
from activitysmith_openapi.models.bad_request_error import BadRequestError
@@ -62,6 +63,7 @@
from activitysmith_openapi.models.live_activity_update_request import LiveActivityUpdateRequest
from activitysmith_openapi.models.live_activity_update_response import LiveActivityUpdateResponse
from activitysmith_openapi.models.live_activity_webhook_method import LiveActivityWebhookMethod
+from activitysmith_openapi.models.metadata_value import MetadataValue
from activitysmith_openapi.models.metric_error import MetricError
from activitysmith_openapi.models.metric_value_update_request import MetricValueUpdateRequest
from activitysmith_openapi.models.metric_value_update_request_value import MetricValueUpdateRequestValue
@@ -76,3 +78,4 @@
from activitysmith_openapi.models.rate_limit_error import RateLimitError
from activitysmith_openapi.models.send_push_notification429_response import SendPushNotification429Response
from activitysmith_openapi.models.stream_content_state import StreamContentState
+from activitysmith_openapi.models.update_app_icon_badge_count422_response import UpdateAppIconBadgeCount422Response
diff --git a/activitysmith_openapi/api/app_icon_badges_api.py b/activitysmith_openapi/api/app_icon_badges_api.py
index 29d22f6..e832526 100644
--- a/activitysmith_openapi/api/app_icon_badges_api.py
+++ b/activitysmith_openapi/api/app_icon_badges_api.py
@@ -94,7 +94,8 @@ def update_app_icon_badge_count(
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "UpdateAppIconBadgeCount422Response",
+ '502': "AppIconBadgeCountUpdateError",
'429': "RateLimitError",
}
response_data = self.api_client.call_api(
@@ -165,7 +166,8 @@ def update_app_icon_badge_count_with_http_info(
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "UpdateAppIconBadgeCount422Response",
+ '502': "AppIconBadgeCountUpdateError",
'429': "RateLimitError",
}
response_data = self.api_client.call_api(
@@ -236,7 +238,8 @@ def update_app_icon_badge_count_without_preload_content(
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "UpdateAppIconBadgeCount422Response",
+ '502': "AppIconBadgeCountUpdateError",
'429': "RateLimitError",
}
response_data = self.api_client.call_api(
diff --git a/activitysmith_openapi/api_client.py b/activitysmith_openapi/api_client.py
index 5132b72..cd1a411 100644
--- a/activitysmith_openapi/api_client.py
+++ b/activitysmith_openapi/api_client.py
@@ -88,7 +88,7 @@ def __init__(
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
- self.user_agent = 'OpenAPI-Generator/1.10.0/python'
+ self.user_agent = 'OpenAPI-Generator/1.11.0/python'
self.client_side_validation = configuration.client_side_validation
def __enter__(self):
diff --git a/activitysmith_openapi/configuration.py b/activitysmith_openapi/configuration.py
index 4d3acda..d1c9eda 100644
--- a/activitysmith_openapi/configuration.py
+++ b/activitysmith_openapi/configuration.py
@@ -382,6 +382,13 @@ def auth_settings(self):
'key': 'Authorization',
'value': 'Bearer ' + self.access_token
}
+ if self.access_token is not None:
+ auth['mcpOAuth'] = {
+ 'type': 'oauth2',
+ 'in': 'header',
+ 'key': 'Authorization',
+ 'value': 'Bearer ' + self.access_token
+ }
return auth
def to_debug_report(self):
@@ -393,7 +400,7 @@ def to_debug_report(self):
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
- "SDK Package Version: 1.10.0".\
+ "SDK Package Version: 1.11.0".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):
diff --git a/activitysmith_openapi/docs/AppIconBadgeCountUpdateError.md b/activitysmith_openapi/docs/AppIconBadgeCountUpdateError.md
new file mode 100644
index 0000000..8aec0cf
--- /dev/null
+++ b/activitysmith_openapi/docs/AppIconBadgeCountUpdateError.md
@@ -0,0 +1,37 @@
+# AppIconBadgeCountUpdateError
+
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**error** | **str** | |
+**code** | **str** | |
+**message** | **str** | |
+**badge** | **int** | |
+**devices_targeted** | **int** | | [optional]
+**devices_updated** | **int** | |
+**users_updated** | **int** | | [optional]
+**devices_notified** | **int** | Deprecated compatibility alias for devices_updated. | [optional]
+**effective_channel_slugs** | **List[str]** | | [optional]
+
+## Example
+
+```python
+from activitysmith_openapi.models.app_icon_badge_count_update_error import AppIconBadgeCountUpdateError
+
+# TODO update the JSON string below
+json = "{}"
+# create an instance of AppIconBadgeCountUpdateError from a JSON string
+app_icon_badge_count_update_error_instance = AppIconBadgeCountUpdateError.from_json(json)
+# print the JSON string representation of the object
+print(AppIconBadgeCountUpdateError.to_json())
+
+# convert the object into a dict
+app_icon_badge_count_update_error_dict = app_icon_badge_count_update_error_instance.to_dict()
+# create an instance of AppIconBadgeCountUpdateError from a dict
+app_icon_badge_count_update_error_from_dict = AppIconBadgeCountUpdateError.from_dict(app_icon_badge_count_update_error_dict)
+```
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/docs/AppIconBadgeCountUpdateResponse.md b/activitysmith_openapi/docs/AppIconBadgeCountUpdateResponse.md
index 74dd986..072e90e 100644
--- a/activitysmith_openapi/docs/AppIconBadgeCountUpdateResponse.md
+++ b/activitysmith_openapi/docs/AppIconBadgeCountUpdateResponse.md
@@ -7,8 +7,10 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**success** | **bool** | |
**badge** | **int** | |
-**devices_notified** | **int** | |
-**users_notified** | **int** | |
+**devices_updated** | **int** | Number of devices whose App Icon Badge Count was updated. |
+**users_updated** | **int** | Number of account users with at least one updated device. |
+**devices_notified** | **int** | Deprecated compatibility alias for devices_updated. | [optional]
+**users_notified** | **int** | Deprecated compatibility alias for users_updated. | [optional]
**effective_channel_slugs** | **List[str]** | |
**timestamp** | **datetime** | |
diff --git a/activitysmith_openapi/docs/AppIconBadgesApi.md b/activitysmith_openapi/docs/AppIconBadgesApi.md
index 6ae3264..76505cf 100644
--- a/activitysmith_openapi/docs/AppIconBadgesApi.md
+++ b/activitysmith_openapi/docs/AppIconBadgesApi.md
@@ -85,7 +85,8 @@ Name | Type | Description | Notes
**200** | App Icon Badge Count updated | - |
**400** | Bad request | - |
**403** | Forbidden | - |
-**404** | No recipients found for effective channel target | - |
+**422** | No matching devices found, or a targeted device needs to reconnect | - |
+**502** | App Icon Badge Count could not be updated | - |
**429** | Rate limit exceeded | - |
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
diff --git a/activitysmith_openapi/docs/LiveActivityEndRequest.md b/activitysmith_openapi/docs/LiveActivityEndRequest.md
index 94f4fa6..c723061 100644
--- a/activitysmith_openapi/docs/LiveActivityEndRequest.md
+++ b/activitysmith_openapi/docs/LiveActivityEndRequest.md
@@ -6,7 +6,9 @@ End an existing Live Activity by activity_id.
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
**activity_id** | **str** | |
+**tags** | **List[str]** | Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them. | [optional]
**content_state** | [**ContentStateEnd**](ContentStateEnd.md) | |
**action** | [**LiveActivityAction**](LiveActivityAction.md) | | [optional]
**secondary_action** | [**LiveActivityAction**](LiveActivityAction.md) | Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action. | [optional]
diff --git a/activitysmith_openapi/docs/LiveActivityLimitError.md b/activitysmith_openapi/docs/LiveActivityLimitError.md
index d661fc6..25cb51a 100644
--- a/activitysmith_openapi/docs/LiveActivityLimitError.md
+++ b/activitysmith_openapi/docs/LiveActivityLimitError.md
@@ -8,7 +8,9 @@ Name | Type | Description | Notes
**error** | **str** | |
**message** | **str** | |
**limit** | **int** | |
-**active** | **int** | Current number of active Live Activities. |
+**active** | **int** | Highest number of active Live Activities among the targeted devices. |
+**blocked_devices** | **int** | Number of targeted devices that have reached the enforced iOS Live Activity concurrency threshold. Included only when targeted devices have mixed capacity. | [optional]
+**targeted_devices** | **int** | Total number of targeted devices. Included only when targeted devices have mixed capacity. | [optional]
## Example
diff --git a/activitysmith_openapi/docs/LiveActivityStartRequest.md b/activitysmith_openapi/docs/LiveActivityStartRequest.md
index 914e655..f8b8e7d 100644
--- a/activitysmith_openapi/docs/LiveActivityStartRequest.md
+++ b/activitysmith_openapi/docs/LiveActivityStartRequest.md
@@ -6,6 +6,7 @@ Start a new Live Activity. The response includes activity_id for later update an
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
**content_state** | [**ContentStateStart**](ContentStateStart.md) | |
**action** | [**LiveActivityAction**](LiveActivityAction.md) | | [optional]
**secondary_action** | [**LiveActivityAction**](LiveActivityAction.md) | Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action. | [optional]
diff --git a/activitysmith_openapi/docs/LiveActivityStreamDeleteRequest.md b/activitysmith_openapi/docs/LiveActivityStreamDeleteRequest.md
index bde073d..d1d068a 100644
--- a/activitysmith_openapi/docs/LiveActivityStreamDeleteRequest.md
+++ b/activitysmith_openapi/docs/LiveActivityStreamDeleteRequest.md
@@ -6,6 +6,8 @@ Optional payload for ending a managed stream. When omitted, ActivitySmith ends t
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
+**tags** | **List[str]** | Optional tags to organize and filter notification history. | [optional]
**content_state** | [**StreamContentState**](StreamContentState.md) | | [optional]
**action** | [**LiveActivityAction**](LiveActivityAction.md) | | [optional]
**secondary_action** | [**LiveActivityAction**](LiveActivityAction.md) | Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action. | [optional]
diff --git a/activitysmith_openapi/docs/LiveActivityStreamRequest.md b/activitysmith_openapi/docs/LiveActivityStreamRequest.md
index 2aba012..2df7c2a 100644
--- a/activitysmith_openapi/docs/LiveActivityStreamRequest.md
+++ b/activitysmith_openapi/docs/LiveActivityStreamRequest.md
@@ -6,6 +6,7 @@ Send the latest state for a managed Live Activity stream. channels is the stream
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
**content_state** | [**StreamContentState**](StreamContentState.md) | |
**action** | [**LiveActivityAction**](LiveActivityAction.md) | | [optional]
**secondary_action** | [**LiveActivityAction**](LiveActivityAction.md) | Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action. | [optional]
diff --git a/activitysmith_openapi/docs/LiveActivityUpdateRequest.md b/activitysmith_openapi/docs/LiveActivityUpdateRequest.md
index 1144734..e579170 100644
--- a/activitysmith_openapi/docs/LiveActivityUpdateRequest.md
+++ b/activitysmith_openapi/docs/LiveActivityUpdateRequest.md
@@ -6,7 +6,9 @@ Update an existing Live Activity by activity_id.
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
**activity_id** | **str** | |
+**tags** | **List[str]** | Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them. | [optional]
**content_state** | [**ContentStateUpdate**](ContentStateUpdate.md) | |
**action** | [**LiveActivityAction**](LiveActivityAction.md) | | [optional]
**secondary_action** | [**LiveActivityAction**](LiveActivityAction.md) | Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action. | [optional]
diff --git a/activitysmith_openapi/docs/MetadataValue.md b/activitysmith_openapi/docs/MetadataValue.md
new file mode 100644
index 0000000..d7e08ed
--- /dev/null
+++ b/activitysmith_openapi/docs/MetadataValue.md
@@ -0,0 +1,28 @@
+# MetadataValue
+
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+
+## Example
+
+```python
+from activitysmith_openapi.models.metadata_value import MetadataValue
+
+# TODO update the JSON string below
+json = "{}"
+# create an instance of MetadataValue from a JSON string
+metadata_value_instance = MetadataValue.from_json(json)
+# print the JSON string representation of the object
+print(MetadataValue.to_json())
+
+# convert the object into a dict
+metadata_value_dict = metadata_value_instance.to_dict()
+# create an instance of MetadataValue from a dict
+metadata_value_from_dict = MetadataValue.from_dict(metadata_value_dict)
+```
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/docs/PushNotificationAction.md b/activitysmith_openapi/docs/PushNotificationAction.md
index a47961f..b1569fd 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** | Action URL. For open_url, use an HTTP or HTTPS URL or a shortcuts://run-shortcut?name=... URL that runs a specific iPhone Shortcut. For webhook, use an HTTPS URL called by the ActivitySmith backend. |
+**url** | **str** | Action URL. For open_url, use HTTP, HTTPS, Shortcuts, or an installed app’s custom URL scheme, such as spotify:// or spotify:track:123. Custom app schemes require iOS 1.13.4 build 2 or later; no web fallback is provided. Internal and executable schemes are blocked. 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** | **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 aa6611c..1c45c9e 100644
--- a/activitysmith_openapi/docs/PushNotificationRequest.md
+++ b/activitysmith_openapi/docs/PushNotificationRequest.md
@@ -5,11 +5,12 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
+**metadata** | [**Dict[str, MetadataValue]**](MetadataValue.md) | Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it. | [optional]
**title** | **str** | |
**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 HTTP URL, HTTPS URL, or shortcuts://run-shortcut?name=... URL opened when the user taps the notification body. Use shortcuts://run-shortcut?name=... to run a specific iPhone Shortcut that already exists on the user's device. Overrides the default tap target from `media` when both are provided. | [optional]
+**redirection** | **str** | Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media. | [optional]
**actions** | [**List[PushNotificationAction]**](PushNotificationAction.md) | Optional interactive actions shown when users expand the notification. Cannot be combined with `media`. | [optional]
**payload** | **object** | | [optional]
**badge** | **int** | | [optional]
diff --git a/activitysmith_openapi/docs/SendPushNotification429Response.md b/activitysmith_openapi/docs/SendPushNotification429Response.md
index e1738e1..b4847aa 100644
--- a/activitysmith_openapi/docs/SendPushNotification429Response.md
+++ b/activitysmith_openapi/docs/SendPushNotification429Response.md
@@ -8,7 +8,9 @@ Name | Type | Description | Notes
**error** | **str** | |
**message** | **str** | |
**limit** | **int** | |
-**active** | **int** | Current number of active Live Activities. |
+**active** | **int** | Highest number of active Live Activities among the targeted devices. |
+**blocked_devices** | **int** | Number of targeted devices that have reached the enforced iOS Live Activity concurrency threshold. Included only when targeted devices have mixed capacity. | [optional]
+**targeted_devices** | **int** | Total number of targeted devices. Included only when targeted devices have mixed capacity. | [optional]
## Example
diff --git a/activitysmith_openapi/docs/UpdateAppIconBadgeCount422Response.md b/activitysmith_openapi/docs/UpdateAppIconBadgeCount422Response.md
new file mode 100644
index 0000000..f7cbbfe
--- /dev/null
+++ b/activitysmith_openapi/docs/UpdateAppIconBadgeCount422Response.md
@@ -0,0 +1,37 @@
+# UpdateAppIconBadgeCount422Response
+
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**error** | **str** | |
+**message** | **str** | |
+**effective_channel_slugs** | **List[str]** | | [optional]
+**code** | **str** | |
+**badge** | **int** | |
+**devices_targeted** | **int** | | [optional]
+**devices_updated** | **int** | |
+**users_updated** | **int** | | [optional]
+**devices_notified** | **int** | Deprecated compatibility alias for devices_updated. | [optional]
+
+## Example
+
+```python
+from activitysmith_openapi.models.update_app_icon_badge_count422_response import UpdateAppIconBadgeCount422Response
+
+# TODO update the JSON string below
+json = "{}"
+# create an instance of UpdateAppIconBadgeCount422Response from a JSON string
+update_app_icon_badge_count422_response_instance = UpdateAppIconBadgeCount422Response.from_json(json)
+# print the JSON string representation of the object
+print(UpdateAppIconBadgeCount422Response.to_json())
+
+# convert the object into a dict
+update_app_icon_badge_count422_response_dict = update_app_icon_badge_count422_response_instance.to_dict()
+# create an instance of UpdateAppIconBadgeCount422Response from a dict
+update_app_icon_badge_count422_response_from_dict = UpdateAppIconBadgeCount422Response.from_dict(update_app_icon_badge_count422_response_dict)
+```
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/models/__init__.py b/activitysmith_openapi/models/__init__.py
index b9576c7..0f8f506 100644
--- a/activitysmith_openapi/models/__init__.py
+++ b/activitysmith_openapi/models/__init__.py
@@ -17,6 +17,7 @@
from activitysmith_openapi.models.activity_metric import ActivityMetric
from activitysmith_openapi.models.activity_metric_value import ActivityMetricValue
from activitysmith_openapi.models.alert_payload import AlertPayload
+from activitysmith_openapi.models.app_icon_badge_count_update_error import AppIconBadgeCountUpdateError
from activitysmith_openapi.models.app_icon_badge_count_update_request import AppIconBadgeCountUpdateRequest
from activitysmith_openapi.models.app_icon_badge_count_update_response import AppIconBadgeCountUpdateResponse
from activitysmith_openapi.models.bad_request_error import BadRequestError
@@ -42,6 +43,7 @@
from activitysmith_openapi.models.live_activity_update_request import LiveActivityUpdateRequest
from activitysmith_openapi.models.live_activity_update_response import LiveActivityUpdateResponse
from activitysmith_openapi.models.live_activity_webhook_method import LiveActivityWebhookMethod
+from activitysmith_openapi.models.metadata_value import MetadataValue
from activitysmith_openapi.models.metric_error import MetricError
from activitysmith_openapi.models.metric_value_update_request import MetricValueUpdateRequest
from activitysmith_openapi.models.metric_value_update_request_value import MetricValueUpdateRequestValue
@@ -56,3 +58,4 @@
from activitysmith_openapi.models.rate_limit_error import RateLimitError
from activitysmith_openapi.models.send_push_notification429_response import SendPushNotification429Response
from activitysmith_openapi.models.stream_content_state import StreamContentState
+from activitysmith_openapi.models.update_app_icon_badge_count422_response import UpdateAppIconBadgeCount422Response
diff --git a/activitysmith_openapi/models/app_icon_badge_count_update_error.py b/activitysmith_openapi/models/app_icon_badge_count_update_error.py
new file mode 100644
index 0000000..0b9c9c7
--- /dev/null
+++ b/activitysmith_openapi/models/app_icon_badge_count_update_error.py
@@ -0,0 +1,111 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import pprint
+import re # noqa: F401
+import json
+
+from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator
+from typing import Any, ClassVar, Dict, List, Optional
+from typing_extensions import Annotated
+from typing import Optional, Set
+from typing_extensions import Self
+
+class AppIconBadgeCountUpdateError(BaseModel):
+ """
+ AppIconBadgeCountUpdateError
+ """ # noqa: E501
+ error: StrictStr
+ code: StrictStr
+ message: StrictStr
+ badge: Annotated[int, Field(le=2147483647, strict=True, ge=0)]
+ devices_targeted: Optional[StrictInt] = None
+ devices_updated: StrictInt
+ users_updated: Optional[StrictInt] = None
+ devices_notified: Optional[StrictInt] = Field(default=None, description="Deprecated compatibility alias for devices_updated.")
+ effective_channel_slugs: Optional[List[StrictStr]] = None
+ __properties: ClassVar[List[str]] = ["error", "code", "message", "badge", "devices_targeted", "devices_updated", "users_updated", "devices_notified", "effective_channel_slugs"]
+
+ @field_validator('code')
+ def code_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(['badge_device_disconnected', 'badge_update_failed']):
+ raise ValueError("must be one of enum values ('badge_device_disconnected', 'badge_update_failed')")
+ return value
+
+ model_config = ConfigDict(
+ populate_by_name=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
+ return json.dumps(self.to_dict())
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of AppIconBadgeCountUpdateError from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([
+ ])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of AppIconBadgeCountUpdateError from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate({
+ "error": obj.get("error"),
+ "code": obj.get("code"),
+ "message": obj.get("message"),
+ "badge": obj.get("badge"),
+ "devices_targeted": obj.get("devices_targeted"),
+ "devices_updated": obj.get("devices_updated"),
+ "users_updated": obj.get("users_updated"),
+ "devices_notified": obj.get("devices_notified"),
+ "effective_channel_slugs": obj.get("effective_channel_slugs")
+ })
+ return _obj
+
+
diff --git a/activitysmith_openapi/models/app_icon_badge_count_update_response.py b/activitysmith_openapi/models/app_icon_badge_count_update_response.py
index 4818c20..e0582ba 100644
--- a/activitysmith_openapi/models/app_icon_badge_count_update_response.py
+++ b/activitysmith_openapi/models/app_icon_badge_count_update_response.py
@@ -19,7 +19,7 @@
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
-from typing import Any, ClassVar, Dict, List
+from typing import Any, ClassVar, Dict, List, Optional
from typing_extensions import Annotated
from typing import Optional, Set
from typing_extensions import Self
@@ -30,11 +30,13 @@ class AppIconBadgeCountUpdateResponse(BaseModel):
""" # noqa: E501
success: StrictBool
badge: Annotated[int, Field(le=2147483647, strict=True, ge=0)]
- devices_notified: StrictInt
- users_notified: StrictInt
+ devices_updated: StrictInt = Field(description="Number of devices whose App Icon Badge Count was updated.")
+ users_updated: StrictInt = Field(description="Number of account users with at least one updated device.")
+ devices_notified: Optional[StrictInt] = Field(default=None, description="Deprecated compatibility alias for devices_updated.")
+ users_notified: Optional[StrictInt] = Field(default=None, description="Deprecated compatibility alias for users_updated.")
effective_channel_slugs: List[StrictStr]
timestamp: datetime
- __properties: ClassVar[List[str]] = ["success", "badge", "devices_notified", "users_notified", "effective_channel_slugs", "timestamp"]
+ __properties: ClassVar[List[str]] = ["success", "badge", "devices_updated", "users_updated", "devices_notified", "users_notified", "effective_channel_slugs", "timestamp"]
model_config = ConfigDict(
populate_by_name=True,
@@ -89,6 +91,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
_obj = cls.model_validate({
"success": obj.get("success"),
"badge": obj.get("badge"),
+ "devices_updated": obj.get("devices_updated"),
+ "users_updated": obj.get("users_updated"),
"devices_notified": obj.get("devices_notified"),
"users_notified": obj.get("users_notified"),
"effective_channel_slugs": obj.get("effective_channel_slugs"),
diff --git a/activitysmith_openapi/models/live_activity_end_request.py b/activitysmith_openapi/models/live_activity_end_request.py
index d9da0dd..4b9d75c 100644
--- a/activitysmith_openapi/models/live_activity_end_request.py
+++ b/activitysmith_openapi/models/live_activity_end_request.py
@@ -19,8 +19,10 @@
from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
+from typing_extensions import Annotated
from activitysmith_openapi.models.content_state_end import ContentStateEnd
from activitysmith_openapi.models.live_activity_action import LiveActivityAction
+from activitysmith_openapi.models.metadata_value import MetadataValue
from typing import Optional, Set
from typing_extensions import Self
@@ -28,11 +30,13 @@ class LiveActivityEndRequest(BaseModel):
"""
End an existing Live Activity by activity_id.
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
activity_id: StrictStr
+ tags: Optional[Annotated[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]], Field(max_length=20)]] = Field(default=None, description="Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them.")
content_state: ContentStateEnd
action: Optional[LiveActivityAction] = None
secondary_action: Optional[LiveActivityAction] = Field(default=None, description="Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action.")
- __properties: ClassVar[List[str]] = ["activity_id", "content_state", "action", "secondary_action"]
+ __properties: ClassVar[List[str]] = ["metadata", "activity_id", "tags", "content_state", "action", "secondary_action"]
model_config = ConfigDict(
populate_by_name=True,
@@ -73,6 +77,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of content_state
if self.content_state:
_dict['content_state'] = self.content_state.to_dict()
@@ -94,7 +105,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
"activity_id": obj.get("activity_id"),
+ "tags": obj.get("tags"),
"content_state": ContentStateEnd.from_dict(obj["content_state"]) if obj.get("content_state") is not None else None,
"action": LiveActivityAction.from_dict(obj["action"]) if obj.get("action") is not None else None,
"secondary_action": LiveActivityAction.from_dict(obj["secondary_action"]) if obj.get("secondary_action") is not None else None
diff --git a/activitysmith_openapi/models/live_activity_limit_error.py b/activitysmith_openapi/models/live_activity_limit_error.py
index f33b7e4..e3862e1 100644
--- a/activitysmith_openapi/models/live_activity_limit_error.py
+++ b/activitysmith_openapi/models/live_activity_limit_error.py
@@ -18,7 +18,7 @@
import json
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
-from typing import Any, ClassVar, Dict, List
+from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self
@@ -29,8 +29,10 @@ class LiveActivityLimitError(BaseModel):
error: StrictStr
message: StrictStr
limit: StrictInt
- active: StrictInt = Field(description="Current number of active Live Activities.")
- __properties: ClassVar[List[str]] = ["error", "message", "limit", "active"]
+ active: StrictInt = Field(description="Highest number of active Live Activities among the targeted devices.")
+ blocked_devices: Optional[StrictInt] = Field(default=None, description="Number of targeted devices that have reached the enforced iOS Live Activity concurrency threshold. Included only when targeted devices have mixed capacity.")
+ targeted_devices: Optional[StrictInt] = Field(default=None, description="Total number of targeted devices. Included only when targeted devices have mixed capacity.")
+ __properties: ClassVar[List[str]] = ["error", "message", "limit", "active", "blocked_devices", "targeted_devices"]
model_config = ConfigDict(
populate_by_name=True,
@@ -86,7 +88,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"error": obj.get("error"),
"message": obj.get("message"),
"limit": obj.get("limit"),
- "active": obj.get("active")
+ "active": obj.get("active"),
+ "blocked_devices": obj.get("blocked_devices"),
+ "targeted_devices": obj.get("targeted_devices")
})
return _obj
diff --git a/activitysmith_openapi/models/live_activity_start_request.py b/activitysmith_openapi/models/live_activity_start_request.py
index 64ad780..1197184 100644
--- a/activitysmith_openapi/models/live_activity_start_request.py
+++ b/activitysmith_openapi/models/live_activity_start_request.py
@@ -24,6 +24,7 @@
from activitysmith_openapi.models.channel_target import ChannelTarget
from activitysmith_openapi.models.content_state_start import ContentStateStart
from activitysmith_openapi.models.live_activity_action import LiveActivityAction
+from activitysmith_openapi.models.metadata_value import MetadataValue
from typing import Optional, Set
from typing_extensions import Self
@@ -31,13 +32,14 @@ class LiveActivityStartRequest(BaseModel):
"""
Start a new Live Activity. The response includes activity_id for later update and end calls.
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
content_state: ContentStateStart
action: Optional[LiveActivityAction] = None
secondary_action: Optional[LiveActivityAction] = Field(default=None, description="Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action.")
alert: Optional[AlertPayload] = None
target: Optional[ChannelTarget] = None
tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
- __properties: ClassVar[List[str]] = ["content_state", "action", "secondary_action", "alert", "target", "tags"]
+ __properties: ClassVar[List[str]] = ["metadata", "content_state", "action", "secondary_action", "alert", "target", "tags"]
model_config = ConfigDict(
populate_by_name=True,
@@ -78,6 +80,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of content_state
if self.content_state:
_dict['content_state'] = self.content_state.to_dict()
@@ -105,6 +114,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
"content_state": ContentStateStart.from_dict(obj["content_state"]) if obj.get("content_state") is not None else None,
"action": LiveActivityAction.from_dict(obj["action"]) if obj.get("action") is not None else None,
"secondary_action": LiveActivityAction.from_dict(obj["secondary_action"]) if obj.get("secondary_action") is not None else None,
diff --git a/activitysmith_openapi/models/live_activity_stream_delete_request.py b/activitysmith_openapi/models/live_activity_stream_delete_request.py
index 8edb49c..b47c57b 100644
--- a/activitysmith_openapi/models/live_activity_stream_delete_request.py
+++ b/activitysmith_openapi/models/live_activity_stream_delete_request.py
@@ -17,10 +17,12 @@
import re # noqa: F401
import json
-from pydantic import BaseModel, ConfigDict, Field
+from pydantic import BaseModel, ConfigDict, Field, field_validator
from typing import Any, ClassVar, Dict, List, Optional
+from typing_extensions import Annotated
from activitysmith_openapi.models.alert_payload import AlertPayload
from activitysmith_openapi.models.live_activity_action import LiveActivityAction
+from activitysmith_openapi.models.metadata_value import MetadataValue
from activitysmith_openapi.models.stream_content_state import StreamContentState
from typing import Optional, Set
from typing_extensions import Self
@@ -29,11 +31,13 @@ class LiveActivityStreamDeleteRequest(BaseModel):
"""
Optional payload for ending a managed stream. When omitted, ActivitySmith ends the stream using the latest known state when possible.
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
+ tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
content_state: Optional[StreamContentState] = None
action: Optional[LiveActivityAction] = None
secondary_action: Optional[LiveActivityAction] = Field(default=None, description="Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action.")
alert: Optional[AlertPayload] = None
- __properties: ClassVar[List[str]] = ["content_state", "action", "secondary_action", "alert"]
+ __properties: ClassVar[List[str]] = ["metadata", "tags", "content_state", "action", "secondary_action", "alert"]
model_config = ConfigDict(
populate_by_name=True,
@@ -74,6 +78,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of content_state
if self.content_state:
_dict['content_state'] = self.content_state.to_dict()
@@ -98,6 +109,13 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
+ "tags": obj.get("tags"),
"content_state": StreamContentState.from_dict(obj["content_state"]) if obj.get("content_state") is not None else None,
"action": LiveActivityAction.from_dict(obj["action"]) if obj.get("action") is not None else None,
"secondary_action": LiveActivityAction.from_dict(obj["secondary_action"]) if obj.get("secondary_action") is not None else None,
diff --git a/activitysmith_openapi/models/live_activity_stream_request.py b/activitysmith_openapi/models/live_activity_stream_request.py
index 7a2e7c4..3b39cad 100644
--- a/activitysmith_openapi/models/live_activity_stream_request.py
+++ b/activitysmith_openapi/models/live_activity_stream_request.py
@@ -23,6 +23,7 @@
from activitysmith_openapi.models.alert_payload import AlertPayload
from activitysmith_openapi.models.channel_target import ChannelTarget
from activitysmith_openapi.models.live_activity_action import LiveActivityAction
+from activitysmith_openapi.models.metadata_value import MetadataValue
from activitysmith_openapi.models.stream_content_state import StreamContentState
from typing import Optional, Set
from typing_extensions import Self
@@ -31,6 +32,7 @@ class LiveActivityStreamRequest(BaseModel):
"""
Send the latest state for a managed Live Activity stream. channels is the streamlined form for stream targeting. target.channels is also accepted for compatibility. If both are provided, they must match.
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
content_state: StreamContentState
action: Optional[LiveActivityAction] = None
secondary_action: Optional[LiveActivityAction] = Field(default=None, description="Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action.")
@@ -38,7 +40,7 @@ class LiveActivityStreamRequest(BaseModel):
channels: Optional[Annotated[List[StrictStr], Field(min_length=1)]] = Field(default=None, description="Channel slugs. When omitted, API key scope determines recipients.")
target: Optional[ChannelTarget] = None
tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
- __properties: ClassVar[List[str]] = ["content_state", "action", "secondary_action", "alert", "channels", "target", "tags"]
+ __properties: ClassVar[List[str]] = ["metadata", "content_state", "action", "secondary_action", "alert", "channels", "target", "tags"]
model_config = ConfigDict(
populate_by_name=True,
@@ -79,6 +81,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of content_state
if self.content_state:
_dict['content_state'] = self.content_state.to_dict()
@@ -106,6 +115,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
"content_state": StreamContentState.from_dict(obj["content_state"]) if obj.get("content_state") is not None else None,
"action": LiveActivityAction.from_dict(obj["action"]) if obj.get("action") is not None else None,
"secondary_action": LiveActivityAction.from_dict(obj["secondary_action"]) if obj.get("secondary_action") is not None else None,
diff --git a/activitysmith_openapi/models/live_activity_update_request.py b/activitysmith_openapi/models/live_activity_update_request.py
index 3ed4911..4c342b9 100644
--- a/activitysmith_openapi/models/live_activity_update_request.py
+++ b/activitysmith_openapi/models/live_activity_update_request.py
@@ -19,8 +19,10 @@
from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
+from typing_extensions import Annotated
from activitysmith_openapi.models.content_state_update import ContentStateUpdate
from activitysmith_openapi.models.live_activity_action import LiveActivityAction
+from activitysmith_openapi.models.metadata_value import MetadataValue
from typing import Optional, Set
from typing_extensions import Self
@@ -28,11 +30,13 @@ class LiveActivityUpdateRequest(BaseModel):
"""
Update an existing Live Activity by activity_id.
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
activity_id: StrictStr
+ tags: Optional[Annotated[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]], Field(max_length=20)]] = Field(default=None, description="Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them.")
content_state: ContentStateUpdate
action: Optional[LiveActivityAction] = None
secondary_action: Optional[LiveActivityAction] = Field(default=None, description="Optional secondary action button. Supported for alert, progress, and segmented_progress Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action.")
- __properties: ClassVar[List[str]] = ["activity_id", "content_state", "action", "secondary_action"]
+ __properties: ClassVar[List[str]] = ["metadata", "activity_id", "tags", "content_state", "action", "secondary_action"]
model_config = ConfigDict(
populate_by_name=True,
@@ -73,6 +77,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of content_state
if self.content_state:
_dict['content_state'] = self.content_state.to_dict()
@@ -94,7 +105,14 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
"activity_id": obj.get("activity_id"),
+ "tags": obj.get("tags"),
"content_state": ContentStateUpdate.from_dict(obj["content_state"]) if obj.get("content_state") is not None else None,
"action": LiveActivityAction.from_dict(obj["action"]) if obj.get("action") is not None else None,
"secondary_action": LiveActivityAction.from_dict(obj["secondary_action"]) if obj.get("secondary_action") is not None else None
diff --git a/activitysmith_openapi/models/metadata_value.py b/activitysmith_openapi/models/metadata_value.py
new file mode 100644
index 0000000..a2053b1
--- /dev/null
+++ b/activitysmith_openapi/models/metadata_value.py
@@ -0,0 +1,161 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import json
+import pprint
+from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr, ValidationError, field_validator
+from typing import Any, List, Optional, Union
+from typing_extensions import Annotated
+from pydantic import StrictStr, Field
+from typing import Union, List, Set, Optional, Dict
+from typing_extensions import Literal, Self
+
+METADATAVALUE_ONE_OF_SCHEMAS = ["bool", "float", "str"]
+
+class MetadataValue(BaseModel):
+ """
+ MetadataValue
+ """
+ # data type: str
+ oneof_schema_1_validator: Optional[Annotated[str, Field(strict=True, max_length=4000)]] = None
+ # data type: float
+ oneof_schema_2_validator: Optional[Union[StrictFloat, StrictInt]] = None
+ # data type: bool
+ oneof_schema_3_validator: Optional[StrictBool] = None
+ actual_instance: Optional[Union[bool, float, str]] = None
+ one_of_schemas: Set[str] = { "bool", "float", "str" }
+
+ model_config = ConfigDict(
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+
+ def __init__(self, *args, **kwargs) -> None:
+ if args:
+ if len(args) > 1:
+ raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`")
+ if kwargs:
+ raise ValueError("If a position argument is used, keyword arguments cannot be used.")
+ super().__init__(actual_instance=args[0])
+ else:
+ super().__init__(**kwargs)
+
+ @field_validator('actual_instance')
+ def actual_instance_must_validate_oneof(cls, v):
+ instance = MetadataValue.model_construct()
+ error_messages = []
+ match = 0
+ # validate data type: str
+ try:
+ instance.oneof_schema_1_validator = v
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ # validate data type: float
+ try:
+ instance.oneof_schema_2_validator = v
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ # validate data type: bool
+ try:
+ instance.oneof_schema_3_validator = v
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ if match > 1:
+ # more than 1 match
+ raise ValueError("Multiple matches found when setting `actual_instance` in MetadataValue with oneOf schemas: bool, float, str. Details: " + ", ".join(error_messages))
+ elif match == 0:
+ # no match
+ raise ValueError("No match found when setting `actual_instance` in MetadataValue with oneOf schemas: bool, float, str. Details: " + ", ".join(error_messages))
+ else:
+ return v
+
+ @classmethod
+ def from_dict(cls, obj: Union[str, Dict[str, Any]]) -> Self:
+ return cls.from_json(json.dumps(obj))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Returns the object represented by the json string"""
+ instance = cls.model_construct()
+ error_messages = []
+ match = 0
+
+ # deserialize data into str
+ try:
+ # validation
+ instance.oneof_schema_1_validator = json.loads(json_str)
+ # assign value to actual_instance
+ instance.actual_instance = instance.oneof_schema_1_validator
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ # deserialize data into float
+ try:
+ # validation
+ instance.oneof_schema_2_validator = json.loads(json_str)
+ # assign value to actual_instance
+ instance.actual_instance = instance.oneof_schema_2_validator
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ # deserialize data into bool
+ try:
+ # validation
+ instance.oneof_schema_3_validator = json.loads(json_str)
+ # assign value to actual_instance
+ instance.actual_instance = instance.oneof_schema_3_validator
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+
+ if match > 1:
+ # more than 1 match
+ raise ValueError("Multiple matches found when deserializing the JSON string into MetadataValue with oneOf schemas: bool, float, str. Details: " + ", ".join(error_messages))
+ elif match == 0:
+ # no match
+ raise ValueError("No match found when deserializing the JSON string into MetadataValue with oneOf schemas: bool, float, str. Details: " + ", ".join(error_messages))
+ else:
+ return instance
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the actual instance"""
+ if self.actual_instance is None:
+ return "null"
+
+ if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json):
+ return self.actual_instance.to_json()
+ else:
+ return json.dumps(self.actual_instance)
+
+ def to_dict(self) -> Optional[Union[Dict[str, Any], bool, float, str]]:
+ """Returns the dict representation of the actual instance"""
+ if self.actual_instance is None:
+ return None
+
+ if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict):
+ return self.actual_instance.to_dict()
+ else:
+ # primitive type
+ return self.actual_instance
+
+ def to_str(self) -> str:
+ """Returns the string representation of the actual instance"""
+ return pprint.pformat(self.model_dump())
+
+
diff --git a/activitysmith_openapi/models/push_notification_action.py b/activitysmith_openapi/models/push_notification_action.py
index efd94cd..5af3e5b 100644
--- a/activitysmith_openapi/models/push_notification_action.py
+++ b/activitysmith_openapi/models/push_notification_action.py
@@ -30,7 +30,7 @@ class PushNotificationAction(BaseModel):
""" # noqa: E501
title: StrictStr = Field(description="Button title displayed in iOS expanded notification UI.")
type: PushNotificationActionType
- url: StrictStr = Field(description="Action URL. For open_url, use an HTTP or HTTPS URL or a shortcuts://run-shortcut?name=... URL that runs a specific iPhone Shortcut. For webhook, use an HTTPS URL called by the ActivitySmith backend.")
+ url: StrictStr = Field(description="Action URL. For open_url, use HTTP, HTTPS, Shortcuts, or an installed app’s custom URL scheme, such as spotify:// or spotify:track:123. Custom app schemes require iOS 1.13.4 build 2 or later; no web fallback is provided. Internal and executable schemes are blocked. 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] = {}
diff --git a/activitysmith_openapi/models/push_notification_request.py b/activitysmith_openapi/models/push_notification_request.py
index 4e83ea0..c62d06c 100644
--- a/activitysmith_openapi/models/push_notification_request.py
+++ b/activitysmith_openapi/models/push_notification_request.py
@@ -21,6 +21,7 @@
from typing import Any, ClassVar, Dict, List, Optional
from typing_extensions import Annotated
from activitysmith_openapi.models.channel_target import ChannelTarget
+from activitysmith_openapi.models.metadata_value import MetadataValue
from activitysmith_openapi.models.push_notification_action import PushNotificationAction
from typing import Optional, Set
from typing_extensions import Self
@@ -29,11 +30,12 @@ class PushNotificationRequest(BaseModel):
"""
PushNotificationRequest
""" # noqa: E501
+ metadata: Optional[Dict[str, MetadataValue]] = Field(default=None, description="Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.")
title: StrictStr
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 HTTP URL, HTTPS URL, or shortcuts://run-shortcut?name=... URL opened when the user taps the notification body. Use shortcuts://run-shortcut?name=... to run a specific iPhone Shortcut that already exists on the user's device. Overrides the default tap target from `media` when both are provided.")
+ redirection: Optional[Annotated[str, Field(strict=True, max_length=2048)]] = Field(default=None, description="Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media.")
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
@@ -41,7 +43,7 @@ class PushNotificationRequest(BaseModel):
target: Optional[ChannelTarget] = None
tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
additional_properties: Dict[str, Any] = {}
- __properties: ClassVar[List[str]] = ["title", "message", "subtitle", "media", "redirection", "actions", "payload", "badge", "sound", "target", "tags"]
+ __properties: ClassVar[List[str]] = ["metadata", "title", "message", "subtitle", "media", "redirection", "actions", "payload", "badge", "sound", "target", "tags"]
@field_validator('media')
def media_validate_regular_expression(cls, value):
@@ -59,8 +61,8 @@ def redirection_validate_regular_expression(cls, value):
if value is None:
return value
- if not re.match(r"^(http|https|shortcuts):\/\/", value):
- raise ValueError(r"must validate the regular expression /^(http|https|shortcuts):\/\//")
+ if not re.match(r"^[A-Za-z][A-Za-z0-9+.-]*:", value):
+ raise ValueError(r"must validate the regular expression /^[A-Za-z][A-Za-z0-9+.-]*:/")
return value
model_config = ConfigDict(
@@ -104,6 +106,13 @@ def to_dict(self) -> Dict[str, Any]:
exclude=excluded_fields,
exclude_none=True,
)
+ # override the default output from pydantic by calling `to_dict()` of each value in metadata (dict)
+ _field_dict = {}
+ if self.metadata:
+ for _key in self.metadata:
+ if self.metadata[_key]:
+ _field_dict[_key] = self.metadata[_key].to_dict()
+ _dict['metadata'] = _field_dict
# override the default output from pydantic by calling `to_dict()` of each item in actions (list)
_items = []
if self.actions:
@@ -131,6 +140,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
return cls.model_validate(obj)
_obj = cls.model_validate({
+ "metadata": dict(
+ (_k, MetadataValue.from_dict(_v))
+ for _k, _v in obj["metadata"].items()
+ )
+ if obj.get("metadata") is not None
+ else None,
"title": obj.get("title"),
"message": obj.get("message"),
"subtitle": obj.get("subtitle"),
diff --git a/activitysmith_openapi/models/update_app_icon_badge_count422_response.py b/activitysmith_openapi/models/update_app_icon_badge_count422_response.py
new file mode 100644
index 0000000..ebf28ab
--- /dev/null
+++ b/activitysmith_openapi/models/update_app_icon_badge_count422_response.py
@@ -0,0 +1,137 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import json
+import pprint
+from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator
+from typing import Any, List, Optional
+from activitysmith_openapi.models.app_icon_badge_count_update_error import AppIconBadgeCountUpdateError
+from activitysmith_openapi.models.no_recipients_error import NoRecipientsError
+from pydantic import StrictStr, Field
+from typing import Union, List, Set, Optional, Dict
+from typing_extensions import Literal, Self
+
+UPDATEAPPICONBADGECOUNT422RESPONSE_ONE_OF_SCHEMAS = ["AppIconBadgeCountUpdateError", "NoRecipientsError"]
+
+class UpdateAppIconBadgeCount422Response(BaseModel):
+ """
+ UpdateAppIconBadgeCount422Response
+ """
+ # data type: NoRecipientsError
+ oneof_schema_1_validator: Optional[NoRecipientsError] = None
+ # data type: AppIconBadgeCountUpdateError
+ oneof_schema_2_validator: Optional[AppIconBadgeCountUpdateError] = None
+ actual_instance: Optional[Union[AppIconBadgeCountUpdateError, NoRecipientsError]] = None
+ one_of_schemas: Set[str] = { "AppIconBadgeCountUpdateError", "NoRecipientsError" }
+
+ model_config = ConfigDict(
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+
+ def __init__(self, *args, **kwargs) -> None:
+ if args:
+ if len(args) > 1:
+ raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`")
+ if kwargs:
+ raise ValueError("If a position argument is used, keyword arguments cannot be used.")
+ super().__init__(actual_instance=args[0])
+ else:
+ super().__init__(**kwargs)
+
+ @field_validator('actual_instance')
+ def actual_instance_must_validate_oneof(cls, v):
+ instance = UpdateAppIconBadgeCount422Response.model_construct()
+ error_messages = []
+ match = 0
+ # validate data type: NoRecipientsError
+ if not isinstance(v, NoRecipientsError):
+ error_messages.append(f"Error! Input type `{type(v)}` is not `NoRecipientsError`")
+ else:
+ match += 1
+ # validate data type: AppIconBadgeCountUpdateError
+ if not isinstance(v, AppIconBadgeCountUpdateError):
+ error_messages.append(f"Error! Input type `{type(v)}` is not `AppIconBadgeCountUpdateError`")
+ else:
+ match += 1
+ if match > 1:
+ # more than 1 match
+ raise ValueError("Multiple matches found when setting `actual_instance` in UpdateAppIconBadgeCount422Response with oneOf schemas: AppIconBadgeCountUpdateError, NoRecipientsError. Details: " + ", ".join(error_messages))
+ elif match == 0:
+ # no match
+ raise ValueError("No match found when setting `actual_instance` in UpdateAppIconBadgeCount422Response with oneOf schemas: AppIconBadgeCountUpdateError, NoRecipientsError. Details: " + ", ".join(error_messages))
+ else:
+ return v
+
+ @classmethod
+ def from_dict(cls, obj: Union[str, Dict[str, Any]]) -> Self:
+ return cls.from_json(json.dumps(obj))
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Returns the object represented by the json string"""
+ instance = cls.model_construct()
+ error_messages = []
+ match = 0
+
+ # deserialize data into NoRecipientsError
+ try:
+ instance.actual_instance = NoRecipientsError.from_json(json_str)
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+ # deserialize data into AppIconBadgeCountUpdateError
+ try:
+ instance.actual_instance = AppIconBadgeCountUpdateError.from_json(json_str)
+ match += 1
+ except (ValidationError, ValueError) as e:
+ error_messages.append(str(e))
+
+ if match > 1:
+ # more than 1 match
+ raise ValueError("Multiple matches found when deserializing the JSON string into UpdateAppIconBadgeCount422Response with oneOf schemas: AppIconBadgeCountUpdateError, NoRecipientsError. Details: " + ", ".join(error_messages))
+ elif match == 0:
+ # no match
+ raise ValueError("No match found when deserializing the JSON string into UpdateAppIconBadgeCount422Response with oneOf schemas: AppIconBadgeCountUpdateError, NoRecipientsError. Details: " + ", ".join(error_messages))
+ else:
+ return instance
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the actual instance"""
+ if self.actual_instance is None:
+ return "null"
+
+ if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json):
+ return self.actual_instance.to_json()
+ else:
+ return json.dumps(self.actual_instance)
+
+ def to_dict(self) -> Optional[Union[Dict[str, Any], AppIconBadgeCountUpdateError, NoRecipientsError]]:
+ """Returns the dict representation of the actual instance"""
+ if self.actual_instance is None:
+ return None
+
+ if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict):
+ return self.actual_instance.to_dict()
+ else:
+ # primitive type
+ return self.actual_instance
+
+ def to_str(self) -> str:
+ """Returns the string representation of the actual instance"""
+ return pprint.pformat(self.model_dump())
+
+
diff --git a/activitysmith_openapi/openapi-source.json b/activitysmith_openapi/openapi-source.json
new file mode 100644
index 0000000..dc05717
--- /dev/null
+++ b/activitysmith_openapi/openapi-source.json
@@ -0,0 +1,6 @@
+{
+ "repository": "ActivitySmithHQ/activitysmith-backend",
+ "commit": "49ad7b083b50f53bc3d81edb77ca6597845160a2",
+ "path": "openapi.json",
+ "sha256": "b7e33cd485df8ddc5d7fb7b57b61106fd6f8ff3ffa7cc7bef4ac1ccbbb0cede7"
+}
diff --git a/activitysmith_openapi/test/test_app_icon_badge_count_update_error.py b/activitysmith_openapi/test/test_app_icon_badge_count_update_error.py
new file mode 100644
index 0000000..b6b6f1e
--- /dev/null
+++ b/activitysmith_openapi/test/test_app_icon_badge_count_update_error.py
@@ -0,0 +1,66 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.app_icon_badge_count_update_error import AppIconBadgeCountUpdateError
+
+class TestAppIconBadgeCountUpdateError(unittest.TestCase):
+ """AppIconBadgeCountUpdateError unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def make_instance(self, include_optional) -> AppIconBadgeCountUpdateError:
+ """Test AppIconBadgeCountUpdateError
+ include_optional is a boolean, when False only required
+ params are included, when True both required and
+ optional params are included """
+ # uncomment below to create an instance of `AppIconBadgeCountUpdateError`
+ """
+ model = AppIconBadgeCountUpdateError()
+ if include_optional:
+ return AppIconBadgeCountUpdateError(
+ error = '',
+ code = 'badge_device_disconnected',
+ message = '',
+ badge = 0,
+ devices_targeted = 56,
+ devices_updated = 56,
+ users_updated = 56,
+ devices_notified = 56,
+ effective_channel_slugs = [
+ ''
+ ]
+ )
+ else:
+ return AppIconBadgeCountUpdateError(
+ error = '',
+ code = 'badge_device_disconnected',
+ message = '',
+ badge = 0,
+ devices_updated = 56,
+ )
+ """
+
+ def testAppIconBadgeCountUpdateError(self):
+ """Test AppIconBadgeCountUpdateError"""
+ # inst_req_only = self.make_instance(include_optional=False)
+ # inst_req_and_optional = self.make_instance(include_optional=True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/activitysmith_openapi/test/test_app_icon_badge_count_update_response.py b/activitysmith_openapi/test/test_app_icon_badge_count_update_response.py
index 82f2555..ada9340 100644
--- a/activitysmith_openapi/test/test_app_icon_badge_count_update_response.py
+++ b/activitysmith_openapi/test/test_app_icon_badge_count_update_response.py
@@ -37,6 +37,8 @@ def make_instance(self, include_optional) -> AppIconBadgeCountUpdateResponse:
return AppIconBadgeCountUpdateResponse(
success = True,
badge = 0,
+ devices_updated = 56,
+ users_updated = 56,
devices_notified = 56,
users_notified = 56,
effective_channel_slugs = [
@@ -48,8 +50,8 @@ def make_instance(self, include_optional) -> AppIconBadgeCountUpdateResponse:
return AppIconBadgeCountUpdateResponse(
success = True,
badge = 0,
- devices_notified = 56,
- users_notified = 56,
+ devices_updated = 56,
+ users_updated = 56,
effective_channel_slugs = [
''
],
diff --git a/activitysmith_openapi/test/test_live_activity_end_request.py b/activitysmith_openapi/test/test_live_activity_end_request.py
index 5e51e63..a03564c 100644
--- a/activitysmith_openapi/test/test_live_activity_end_request.py
+++ b/activitysmith_openapi/test/test_live_activity_end_request.py
@@ -35,7 +35,13 @@ def make_instance(self, include_optional) -> LiveActivityEndRequest:
model = LiveActivityEndRequest()
if include_optional:
return LiveActivityEndRequest(
+ metadata = {
+ 'key' : null
+ },
activity_id = '',
+ tags = [
+ '0'
+ ],
content_state = activitysmith_openapi.models.content_state_end.ContentStateEnd(
title = '',
subtitle = '',
diff --git a/activitysmith_openapi/test/test_live_activity_limit_error.py b/activitysmith_openapi/test/test_live_activity_limit_error.py
index c618fef..99a9502 100644
--- a/activitysmith_openapi/test/test_live_activity_limit_error.py
+++ b/activitysmith_openapi/test/test_live_activity_limit_error.py
@@ -38,7 +38,9 @@ def make_instance(self, include_optional) -> LiveActivityLimitError:
error = '',
message = '',
limit = 56,
- active = 56
+ active = 56,
+ blocked_devices = 56,
+ targeted_devices = 56
)
else:
return LiveActivityLimitError(
diff --git a/activitysmith_openapi/test/test_live_activity_start_request.py b/activitysmith_openapi/test/test_live_activity_start_request.py
index beb6dcb..cb9b91a 100644
--- a/activitysmith_openapi/test/test_live_activity_start_request.py
+++ b/activitysmith_openapi/test/test_live_activity_start_request.py
@@ -35,6 +35,9 @@ def make_instance(self, include_optional) -> LiveActivityStartRequest:
model = LiveActivityStartRequest()
if include_optional:
return LiveActivityStartRequest(
+ metadata = {
+ 'key' : null
+ },
content_state = activitysmith_openapi.models.content_state_start.ContentStateStart(
title = '',
subtitle = '',
diff --git a/activitysmith_openapi/test/test_live_activity_stream_delete_request.py b/activitysmith_openapi/test/test_live_activity_stream_delete_request.py
index dc33c0a..3238961 100644
--- a/activitysmith_openapi/test/test_live_activity_stream_delete_request.py
+++ b/activitysmith_openapi/test/test_live_activity_stream_delete_request.py
@@ -35,6 +35,12 @@ def make_instance(self, include_optional) -> LiveActivityStreamDeleteRequest:
model = LiveActivityStreamDeleteRequest()
if include_optional:
return LiveActivityStreamDeleteRequest(
+ metadata = {
+ 'key' : null
+ },
+ tags = [
+ 'gB:9JLe6iL71-aa-.Ctq:dcsc.3-8:1gAa8Xa6u61ArrlGpCQjkQVRmfnjddwcDM0'
+ ],
content_state = activitysmith_openapi.models.stream_content_state.StreamContentState(
title = '',
subtitle = '',
diff --git a/activitysmith_openapi/test/test_live_activity_stream_request.py b/activitysmith_openapi/test/test_live_activity_stream_request.py
index d63a709..bc88f8f 100644
--- a/activitysmith_openapi/test/test_live_activity_stream_request.py
+++ b/activitysmith_openapi/test/test_live_activity_stream_request.py
@@ -35,6 +35,9 @@ def make_instance(self, include_optional) -> LiveActivityStreamRequest:
model = LiveActivityStreamRequest()
if include_optional:
return LiveActivityStreamRequest(
+ metadata = {
+ 'key' : null
+ },
content_state = activitysmith_openapi.models.stream_content_state.StreamContentState(
title = '',
subtitle = '',
diff --git a/activitysmith_openapi/test/test_live_activity_update_request.py b/activitysmith_openapi/test/test_live_activity_update_request.py
index aa6241a..9200dc8 100644
--- a/activitysmith_openapi/test/test_live_activity_update_request.py
+++ b/activitysmith_openapi/test/test_live_activity_update_request.py
@@ -35,7 +35,13 @@ def make_instance(self, include_optional) -> LiveActivityUpdateRequest:
model = LiveActivityUpdateRequest()
if include_optional:
return LiveActivityUpdateRequest(
+ metadata = {
+ 'key' : null
+ },
activity_id = '',
+ tags = [
+ '0'
+ ],
content_state = activitysmith_openapi.models.content_state_update.ContentStateUpdate(
title = '',
subtitle = '',
diff --git a/activitysmith_openapi/test/test_metadata_value.py b/activitysmith_openapi/test/test_metadata_value.py
new file mode 100644
index 0000000..e7115d2
--- /dev/null
+++ b/activitysmith_openapi/test/test_metadata_value.py
@@ -0,0 +1,50 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.metadata_value import MetadataValue
+
+class TestMetadataValue(unittest.TestCase):
+ """MetadataValue unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def make_instance(self, include_optional) -> MetadataValue:
+ """Test MetadataValue
+ include_optional is a boolean, when False only required
+ params are included, when True both required and
+ optional params are included """
+ # uncomment below to create an instance of `MetadataValue`
+ """
+ model = MetadataValue()
+ if include_optional:
+ return MetadataValue(
+ )
+ else:
+ return MetadataValue(
+ )
+ """
+
+ def testMetadataValue(self):
+ """Test MetadataValue"""
+ # inst_req_only = self.make_instance(include_optional=False)
+ # inst_req_and_optional = self.make_instance(include_optional=True)
+
+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 7139f55..803435e 100644
--- a/activitysmith_openapi/test/test_push_notification_request.py
+++ b/activitysmith_openapi/test/test_push_notification_request.py
@@ -35,11 +35,14 @@ def make_instance(self, include_optional) -> PushNotificationRequest:
model = PushNotificationRequest()
if include_optional:
return PushNotificationRequest(
+ metadata = {
+ 'key' : null
+ },
title = '',
message = '',
subtitle = '',
media = 'https:/',
- redirection = 'shortcuts:/',
+ redirection = 'A5bTTFjjMRwg.Zbs8YayHLrJdgMvb:',
actions = [
{
'key' : null
diff --git a/activitysmith_openapi/test/test_send_push_notification429_response.py b/activitysmith_openapi/test/test_send_push_notification429_response.py
index 5466f95..d47464f 100644
--- a/activitysmith_openapi/test/test_send_push_notification429_response.py
+++ b/activitysmith_openapi/test/test_send_push_notification429_response.py
@@ -38,7 +38,9 @@ def make_instance(self, include_optional) -> SendPushNotification429Response:
error = '',
message = '',
limit = 56,
- active = 56
+ active = 56,
+ blocked_devices = 56,
+ targeted_devices = 56
)
else:
return SendPushNotification429Response(
diff --git a/activitysmith_openapi/test/test_update_app_icon_badge_count422_response.py b/activitysmith_openapi/test/test_update_app_icon_badge_count422_response.py
new file mode 100644
index 0000000..23bea2e
--- /dev/null
+++ b/activitysmith_openapi/test/test_update_app_icon_badge_count422_response.py
@@ -0,0 +1,66 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.update_app_icon_badge_count422_response import UpdateAppIconBadgeCount422Response
+
+class TestUpdateAppIconBadgeCount422Response(unittest.TestCase):
+ """UpdateAppIconBadgeCount422Response unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def make_instance(self, include_optional) -> UpdateAppIconBadgeCount422Response:
+ """Test UpdateAppIconBadgeCount422Response
+ include_optional is a boolean, when False only required
+ params are included, when True both required and
+ optional params are included """
+ # uncomment below to create an instance of `UpdateAppIconBadgeCount422Response`
+ """
+ model = UpdateAppIconBadgeCount422Response()
+ if include_optional:
+ return UpdateAppIconBadgeCount422Response(
+ error = '',
+ message = '',
+ effective_channel_slugs = [
+ ''
+ ],
+ code = 'badge_device_disconnected',
+ badge = 0,
+ devices_targeted = 56,
+ devices_updated = 56,
+ users_updated = 56,
+ devices_notified = 56
+ )
+ else:
+ return UpdateAppIconBadgeCount422Response(
+ error = '',
+ message = '',
+ code = 'badge_device_disconnected',
+ badge = 0,
+ devices_updated = 56,
+ )
+ """
+
+ def testUpdateAppIconBadgeCount422Response(self):
+ """Test UpdateAppIconBadgeCount422Response"""
+ # inst_req_only = self.make_instance(include_optional=False)
+ # inst_req_and_optional = self.make_instance(include_optional=True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pyproject.toml b/pyproject.toml
index 50a91e6..60aeeff 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "activitysmith"
-version = "1.10.0"
+version = "1.11.0"
description = "Official ActivitySmith Python SDK"
readme = "README.md"
requires-python = ">=3.9"
diff --git a/tests/test_resources.py b/tests/test_resources.py
index b9b728c..555d8b8 100644
--- a/tests/test_resources.py
+++ b/tests/test_resources.py
@@ -1,3 +1,4 @@
+import json
from importlib.metadata import version
from activitysmith.client import ActivitySmith, action, alert_badge, alert_icon, content_state, metric
@@ -20,22 +21,27 @@ def __init__(self, _api_client):
self.calls = []
def start_live_activity(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(("start", kwargs))
return kwargs
def update_live_activity(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(("update", kwargs))
return kwargs
def end_live_activity(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(("end", kwargs))
return kwargs
def reconcile_live_activity_stream(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(("stream", kwargs))
return kwargs
def end_live_activity_stream(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(("end_stream", kwargs))
return kwargs
@@ -46,6 +52,7 @@ def __init__(self, _api_client):
self.calls = []
def update_metric_value(self, **kwargs):
+ kwargs = json.loads(json.dumps(kwargs, default=lambda value: value.to_dict()))
self.calls.append(kwargs)
return kwargs
diff --git a/tests/test_value_serialization.py b/tests/test_value_serialization.py
new file mode 100644
index 0000000..b4aa215
--- /dev/null
+++ b/tests/test_value_serialization.py
@@ -0,0 +1,144 @@
+from copy import deepcopy
+
+import pytest
+
+from activitysmith import ActivitySmith
+from activitysmith.client import content_state, metric
+from activitysmith_openapi.api_client import ApiClient
+from activitysmith_openapi.models.activity_metric_value import ActivityMetricValue
+from activitysmith_openapi.models.metric_value_update_request_value import MetricValueUpdateRequestValue
+from activitysmith_openapi.models.metric_value_update_request import MetricValueUpdateRequest
+
+
+class RequestCaptured(Exception):
+ pass
+
+
+@pytest.fixture
+def requests(monkeypatch):
+ captured = []
+
+ def capture(self, method, url, header_params=None, body=None, post_params=None, **kwargs):
+ captured.append(body)
+ raise RequestCaptured
+
+ monkeypatch.setattr(ApiClient, "call_api", capture)
+ return captured
+
+
+@pytest.mark.parametrize("value", [0, 42, 3.5, "Healthy"])
+@pytest.mark.parametrize("method", ["start", "update", "end", "stream", "end_stream"])
+@pytest.mark.parametrize("form", ["dict", "named", "helper", "wrapped"])
+def test_live_activity_metric_serializes_native_values(requests, value, method, form):
+ activitysmith = ActivitySmith(api_key="test")
+ state = {"title": "Status", "type": "stats", "metrics": [{"label": "Value", "value": value}]}
+ if form == "wrapped":
+ state["metrics"][0]["value"] = ActivityMetricValue(value)
+ if form == "helper":
+ state = content_state("Status", type="stats", metrics=[metric("Value", value)])
+ original = deepcopy(state)
+ request = {"content_state": state}
+ if method in ("update", "end"):
+ request["activity_id"] = "activity-1"
+ args = ["status"] if method in ("stream", "end_stream") else []
+ with pytest.raises(RequestCaptured):
+ if form == "named":
+ kwargs = dict(state)
+ if method in ("update", "end"):
+ kwargs["activity_id"] = "activity-1"
+ getattr(activitysmith.live_activities, method)(*args, **kwargs)
+ else:
+ getattr(activitysmith.live_activities, method)(*args, request)
+ assert requests[-1]["content_state"]["metrics"][0]["value"] == value
+ assert state == original
+
+
+@pytest.mark.parametrize("value", [0, 42, 3.5, "Healthy"])
+@pytest.mark.parametrize("form", ["scalar", "dict", "wrapped", "model", "legacy"])
+def test_widget_metric_serializes_native_values(requests, value, form):
+ activitysmith = ActivitySmith(api_key="test")
+ request = {"value": value, "timestamp": "2026-09-15T00:00:00Z"}
+ if form == "wrapped":
+ request["value"] = MetricValueUpdateRequestValue(value)
+ if form == "model":
+ request = MetricValueUpdateRequest(value=MetricValueUpdateRequestValue(value))
+ original = deepcopy(request)
+ with pytest.raises(RequestCaptured):
+ if form == "scalar":
+ activitysmith.metrics.update("status", value)
+ elif form == "legacy":
+ activitysmith.metrics.update_metric_value("status", request)
+ else:
+ activitysmith.metrics.update("status", request)
+ assert requests[-1]["value"] == value
+ assert request == original
+
+
+@pytest.mark.parametrize("value", [True, [], {}])
+def test_invalid_metric_values_do_not_reach_transport(requests, value):
+ activitysmith = ActivitySmith(api_key="test")
+ with pytest.raises(ValueError):
+ activitysmith.metrics.update("status", value)
+ assert requests == []
+
+
+@pytest.mark.parametrize("method", ["update", "end"])
+@pytest.mark.parametrize("tags", [None, ["billing"], []])
+@pytest.mark.parametrize("form", ["dict", "named"])
+def test_legacy_tags_serialized(requests, method, tags, form):
+ client = ActivitySmith(api_key="test")
+ fields = {"activity_id": "activity-1", "content_state": {"title": "Job"}}
+ if tags is not None:
+ fields["tags"] = tags
+ with pytest.raises(RequestCaptured):
+ if form == "named":
+ getattr(client.live_activities, method)(**fields)
+ else:
+ getattr(client.live_activities, method)(fields)
+ assert ("tags" in requests[-1]) == (tags is not None)
+ if tags is not None:
+ assert requests[-1]["tags"] == tags
+
+
+@pytest.mark.parametrize("method", ["send", "start", "update", "end", "stream", "end_stream"])
+@pytest.mark.parametrize("metadata", [None, {}, {"order": "382", "ready": False, "count": 0, "empty": "", "ratio": 1.25}])
+@pytest.mark.parametrize("form", ["dict", "named"])
+def test_metadata_serialization(requests, method, metadata, form):
+ client = ActivitySmith(api_key="test")
+ fields = {"title": "Job"} if method == "send" else {"content_state": {"title": "Job", "type": "progress"}}
+ if method in ("update", "end"):
+ fields["activity_id"] = "activity-1"
+ if metadata is not None:
+ fields["metadata"] = metadata
+ resource = client.notifications if method == "send" else client.live_activities
+ args = ["job"] if method in ("stream", "end_stream") else []
+ with pytest.raises(RequestCaptured):
+ if form == "named":
+ getattr(resource, method)(*args, **fields)
+ else:
+ getattr(resource, method)(*args, fields)
+ body = requests[-1]
+ assert ("metadata" in body) == (metadata is not None)
+ if metadata is not None:
+ assert body["metadata"] == metadata
+ assert body["metadata"].get("ready", False) is False
+ assert "metadata" not in body.get("content_state", {})
+
+
+@pytest.mark.parametrize("url", ["http://example.com", "https://example.com", "shortcuts://run-shortcut?name=Test", "spotify://", "spotify:track:123"])
+def test_external_push_urls(requests, url):
+ client = ActivitySmith(api_key="test")
+ with pytest.raises(RequestCaptured):
+ client.notifications.send(title="Job", redirection=url, actions=[{"title":"Open", "type":"open_url", "url":url}])
+ assert requests[-1]["redirection"] == url
+ assert requests[-1]["actions"][0]["url"] == url
+
+@pytest.mark.parametrize("tags", [None, [], ["finished"]])
+def test_end_stream_tags(requests, tags):
+ client = ActivitySmith(api_key="test")
+ with pytest.raises(RequestCaptured):
+ client.live_activities.end_stream("job", tags=tags, metadata={})
+ assert ("tags" in requests[-1]) == (tags is not None)
+ if tags is not None:
+ assert requests[-1]["tags"] == tags
+ assert requests[-1]["metadata"] == {}