From 5330f7037e4c9896e92f2e0b71d0e2c4f0219045 Mon Sep 17 00:00:00 2001 From: strohganoff Date: Wed, 11 Nov 2026 14:29:01 -0700 Subject: [PATCH 1/3] Add contextual payload event model mixins --- streamdeck/models/events/common.py | 45 ++++++++++++++++---- streamdeck/models/events/dials.py | 20 +++++++-- streamdeck/models/events/keys.py | 18 ++++++-- streamdeck/models/events/settings.py | 15 ++++--- streamdeck/models/events/title_parameters.py | 8 +++- streamdeck/models/events/touch_tap.py | 8 +++- streamdeck/models/events/visibility.py | 18 ++++++-- 7 files changed, 102 insertions(+), 30 deletions(-) diff --git a/streamdeck/models/events/common.py b/streamdeck/models/events/common.py index 0295bf9..b613c97 100644 --- a/streamdeck/models/events/common.py +++ b/streamdeck/models/events/common.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC -from typing import Annotated, Generic, Literal, NamedTuple, Union +from typing import Annotated, Generic, Literal, NamedTuple, TypeAlias, Union from pydantic import Field from typing_extensions import TypedDict, TypeVar @@ -33,30 +33,37 @@ class DeviceSpecificEventMixin: ## Payload models and metadata used by multiple event models. - - - - EncoderControllerType = Literal["Encoder"] """The 'Encoder' controller type refers to a dial or touchscreen on a 'Stream Deck +' device.""" + + KeypadControllerType = Literal["Keypad"] """The 'Keypad' controller type refers to a standard action on a Stream Deck device, e.g. buttons or a pedal.""" + + ControllerType = Literal[EncoderControllerType, KeypadControllerType] """Defines the controller type the action is applicable to.""" + CT = TypeVar("CT", bound=ControllerType, default=ControllerType) -class BasePayload(ConfiguredBaseModel, Generic[CT], ABC): - """Base class for all complex payload models.""" +class SettingsPayloadMixin: + """Mixin for payload models that carry plugin-defined settings.""" + + settings: PluginDefinedData + """Settings associated with a plugin or action instance.""" + + +class BasePayload(ConfiguredBaseModel, Generic[CT], ABC, SettingsPayloadMixin): + """Base class for action payload models with controller and settings data.""" + controller: CT """Defines the controller type the action is applicable to. 'Keypad' refers to a standard action on a Stream Deck device, e.g. buttons or a pedal. 'Encoder' refers to a dial / touchscreen on a 'Stream Deck +' device. """ - settings: PluginDefinedData - """Settings associated with the action instance.""" class CoordinatesDict(TypedDict): @@ -107,6 +114,26 @@ class StatefulActionPayloadMixin: """ +ContextualPayload: TypeAlias = ( + SettingsPayloadMixin | StatefulActionPayloadMixin | CoordinatesPayloadMixin +) +"""Payload mixins that provide values copied into action-instance contexts.""" + + +ContextualPayloadT = TypeVar( + "ContextualPayloadT", + bound=ContextualPayload, + default=ContextualPayload, +) + + +class ContextualPayloadEventMixin(ContextualEventMixin, Generic[ContextualPayloadT]): + """Mixin for contextual event models with payloads that update action-instance context.""" + + payload: ContextualPayloadT + """Payload data associated with the contextual event.""" + + class SingleActionPayloadMixin: """Mixin class for event models that have a single action payload.""" diff --git a/streamdeck/models/events/dials.py b/streamdeck/models/events/dials.py index bc7bcff..b9baa78 100644 --- a/streamdeck/models/events/dials.py +++ b/streamdeck/models/events/dials.py @@ -5,7 +5,7 @@ from streamdeck.models.events.base import EventBase from streamdeck.models.events.common import ( BasePayload, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, EncoderControllerType, @@ -31,21 +31,33 @@ class DialRotatePayload(EncoderPayload): ## Event models for DialDown, DialRotate, and DialUp events -class DialDown(EventBase[Literal["dialDown"]], ContextualEventMixin, DeviceSpecificEventMixin): +class DialDown( + EventBase[Literal["dialDown"]], + ContextualPayloadEventMixin[EncoderPayload], + DeviceSpecificEventMixin, +): """Occurs when the user presses a dial (Stream Deck +).""" payload: EncoderPayload """Contextualized information for this event.""" -class DialRotate(EventBase[Literal["dialRotate"]], ContextualEventMixin, DeviceSpecificEventMixin): +class DialRotate( + EventBase[Literal["dialRotate"]], + ContextualPayloadEventMixin[DialRotatePayload], + DeviceSpecificEventMixin, +): """Occurs when the user rotates a dial (Stream Deck +).""" payload: DialRotatePayload """Contextualized information for this event.""" -class DialUp(EventBase[Literal["dialUp"]], ContextualEventMixin, DeviceSpecificEventMixin): +class DialUp( + EventBase[Literal["dialUp"]], + ContextualPayloadEventMixin[EncoderPayload], + DeviceSpecificEventMixin, +): """Occurs when the user releases a pressed dial (Stream Deck +).""" payload: EncoderPayload diff --git a/streamdeck/models/events/keys.py b/streamdeck/models/events/keys.py index c874d95..9232840 100644 --- a/streamdeck/models/events/keys.py +++ b/streamdeck/models/events/keys.py @@ -8,7 +8,7 @@ from streamdeck.models.events.common import ( BasePayload, CardinalityDiscriminated, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, KeypadControllerType, @@ -50,14 +50,26 @@ class MultiActionKeyGesturePayload( ## Event models for KeyDown and KeyUp events -class KeyDown(EventBase[Literal["keyDown"]], ContextualEventMixin, DeviceSpecificEventMixin): +class KeyDown( + EventBase[Literal["keyDown"]], + ContextualPayloadEventMixin[ + CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] + ], + DeviceSpecificEventMixin, +): """Occurs when the user presses a action down.""" payload: CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] """Contextualized information for this event.""" -class KeyUp(EventBase[Literal["keyUp"]], ContextualEventMixin, DeviceSpecificEventMixin): +class KeyUp( + EventBase[Literal["keyUp"]], + ContextualPayloadEventMixin[ + CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] + ], + DeviceSpecificEventMixin, +): """Occurs when the user releases a pressed action.""" payload: CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] diff --git a/streamdeck/models/events/settings.py b/streamdeck/models/events/settings.py index f066a4d..ab98c34 100644 --- a/streamdeck/models/events/settings.py +++ b/streamdeck/models/events/settings.py @@ -6,15 +6,15 @@ from streamdeck.models.events.common import ( BasePayload, CardinalityDiscriminated, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, KeypadControllerType, MultiActionPayloadMixin, + SettingsPayloadMixin, SingleActionPayloadMixin, StatefulActionPayloadMixin, ) -from streamdeck.types import PluginDefinedData # noqa: TC001 ## Models for didReceiveSettings event and its specific payloads. @@ -41,7 +41,11 @@ class MultiActionSettingsPayload( class DidReceiveSettings( - EventBase[Literal["didReceiveSettings"]], ContextualEventMixin, DeviceSpecificEventMixin + EventBase[Literal["didReceiveSettings"]], + ContextualPayloadEventMixin[ + CardinalityDiscriminated[SingleActionSettingsPayload, MultiActionSettingsPayload] + ], + DeviceSpecificEventMixin, ): """Occurs when the settings associated with an action instance are requested, or when the the settings were updated by the property inspector.""" @@ -52,12 +56,9 @@ class DidReceiveSettings( ## Models for didReceiveGlobalSettings event and its specific payload. -class GlobalSettingsPayload(ConfiguredBaseModel): +class GlobalSettingsPayload(ConfiguredBaseModel, SettingsPayloadMixin): """Additional information about the didReceiveGlobalSettings event that occurred.""" - settings: PluginDefinedData - """The global settings received from the Stream Deck.""" - class DidReceiveGlobalSettings(EventBase[Literal["didReceiveGlobalSettings"]]): """Occurs when the plugin receives the global settings from the Stream Deck.""" diff --git a/streamdeck/models/events/title_parameters.py b/streamdeck/models/events/title_parameters.py index b7c9c7f..76a7eee 100644 --- a/streamdeck/models/events/title_parameters.py +++ b/streamdeck/models/events/title_parameters.py @@ -7,7 +7,7 @@ from streamdeck.models.events.base import ConfiguredBaseModel, EventBase from streamdeck.models.events.common import ( BasePayload, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, StatefulActionPayloadMixin, @@ -50,7 +50,11 @@ class TitleParametersDidChangePayload( """Defines aesthetic properties that determine how the title should be rendered.""" -class TitleParametersDidChange(EventBase[Literal["titleParametersDidChange"]], ContextualEventMixin, DeviceSpecificEventMixin): # fmt: skip +class TitleParametersDidChange( + EventBase[Literal["titleParametersDidChange"]], + ContextualPayloadEventMixin[TitleParametersDidChangePayload], + DeviceSpecificEventMixin, +): """Occurs when the user updates an action's title settings in the Stream Deck application.""" payload: TitleParametersDidChangePayload diff --git a/streamdeck/models/events/touch_tap.py b/streamdeck/models/events/touch_tap.py index b3717fa..d3faf34 100644 --- a/streamdeck/models/events/touch_tap.py +++ b/streamdeck/models/events/touch_tap.py @@ -7,7 +7,7 @@ from streamdeck.models.events.base import EventBase from streamdeck.models.events.common import ( BasePayload, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, EncoderControllerType, @@ -23,7 +23,11 @@ class TouchTapPayload(BasePayload[EncoderControllerType], CoordinatesPayloadMixi """Coordinates of where the touchscreen tap occurred, relative to the canvas of the action.""" -class TouchTap(EventBase[Literal["touchTap"]], ContextualEventMixin, DeviceSpecificEventMixin): +class TouchTap( + EventBase[Literal["touchTap"]], + ContextualPayloadEventMixin[TouchTapPayload], + DeviceSpecificEventMixin, +): """Occurs when the user taps the touchscreen (Stream Deck +).""" payload: TouchTapPayload diff --git a/streamdeck/models/events/visibility.py b/streamdeck/models/events/visibility.py index d0396f7..97527e0 100644 --- a/streamdeck/models/events/visibility.py +++ b/streamdeck/models/events/visibility.py @@ -6,7 +6,7 @@ from streamdeck.models.events.common import ( BasePayload, CardinalityDiscriminated, - ContextualEventMixin, + ContextualPayloadEventMixin, CoordinatesPayloadMixin, DeviceSpecificEventMixin, KeypadControllerType, @@ -36,7 +36,13 @@ class MultiActionVisibilityPayload( ## Event models for WillAppear and WillDisappear events -class WillAppear(EventBase[Literal["willAppear"]], ContextualEventMixin, DeviceSpecificEventMixin): +class WillAppear( + EventBase[Literal["willAppear"]], + ContextualPayloadEventMixin[ + CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] + ], + DeviceSpecificEventMixin, +): """Occurs when an action appears on the Stream Deck due to the user navigating to another page, profile, folder, etc. This also occurs during startup if the action is on the "front page". @@ -46,7 +52,13 @@ class WillAppear(EventBase[Literal["willAppear"]], ContextualEventMixin, DeviceS payload: CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] -class WillDisappear(EventBase[Literal["willDisappear"]], ContextualEventMixin, DeviceSpecificEventMixin): # fmt: skip +class WillDisappear( + EventBase[Literal["willDisappear"]], + ContextualPayloadEventMixin[ + CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] + ], + DeviceSpecificEventMixin, +): """Occurs when an action disappears from the Stream Deck due to the user navigating to another page, profile, folder, etc. An action refers to all types of actions, e.g. keys, dials, touchscreens, pedals, etc. From c357a1d9b56e0e27776cdf31cf98fa6bd086a2cc Mon Sep 17 00:00:00 2001 From: strohganoff Date: Wed, 11 Nov 2026 15:17:48 -0700 Subject: [PATCH 2/3] Keep contextual event model declarations on one line --- streamdeck/models/events/dials.py | 18 +++--------------- streamdeck/models/events/keys.py | 16 ++-------------- streamdeck/models/events/settings.py | 8 +------- streamdeck/models/events/title_parameters.py | 6 +----- streamdeck/models/events/touch_tap.py | 6 +----- streamdeck/models/events/visibility.py | 16 ++-------------- 6 files changed, 10 insertions(+), 60 deletions(-) diff --git a/streamdeck/models/events/dials.py b/streamdeck/models/events/dials.py index b9baa78..581747e 100644 --- a/streamdeck/models/events/dials.py +++ b/streamdeck/models/events/dials.py @@ -31,33 +31,21 @@ class DialRotatePayload(EncoderPayload): ## Event models for DialDown, DialRotate, and DialUp events -class DialDown( - EventBase[Literal["dialDown"]], - ContextualPayloadEventMixin[EncoderPayload], - DeviceSpecificEventMixin, -): +class DialDown(EventBase[Literal["dialDown"]], ContextualPayloadEventMixin[EncoderPayload], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user presses a dial (Stream Deck +).""" payload: EncoderPayload """Contextualized information for this event.""" -class DialRotate( - EventBase[Literal["dialRotate"]], - ContextualPayloadEventMixin[DialRotatePayload], - DeviceSpecificEventMixin, -): +class DialRotate(EventBase[Literal["dialRotate"]], ContextualPayloadEventMixin[DialRotatePayload], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user rotates a dial (Stream Deck +).""" payload: DialRotatePayload """Contextualized information for this event.""" -class DialUp( - EventBase[Literal["dialUp"]], - ContextualPayloadEventMixin[EncoderPayload], - DeviceSpecificEventMixin, -): +class DialUp(EventBase[Literal["dialUp"]], ContextualPayloadEventMixin[EncoderPayload], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user releases a pressed dial (Stream Deck +).""" payload: EncoderPayload diff --git a/streamdeck/models/events/keys.py b/streamdeck/models/events/keys.py index 9232840..be3440a 100644 --- a/streamdeck/models/events/keys.py +++ b/streamdeck/models/events/keys.py @@ -50,26 +50,14 @@ class MultiActionKeyGesturePayload( ## Event models for KeyDown and KeyUp events -class KeyDown( - EventBase[Literal["keyDown"]], - ContextualPayloadEventMixin[ - CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] - ], - DeviceSpecificEventMixin, -): +class KeyDown(EventBase[Literal["keyDown"]], ContextualPayloadEventMixin[CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload]], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user presses a action down.""" payload: CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] """Contextualized information for this event.""" -class KeyUp( - EventBase[Literal["keyUp"]], - ContextualPayloadEventMixin[ - CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] - ], - DeviceSpecificEventMixin, -): +class KeyUp(EventBase[Literal["keyUp"]], ContextualPayloadEventMixin[CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload]], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user releases a pressed action.""" payload: CardinalityDiscriminated[SingleActionKeyGesturePayload, MultiActionKeyGesturePayload] diff --git a/streamdeck/models/events/settings.py b/streamdeck/models/events/settings.py index ab98c34..2f1ace9 100644 --- a/streamdeck/models/events/settings.py +++ b/streamdeck/models/events/settings.py @@ -40,13 +40,7 @@ class MultiActionSettingsPayload( """ -class DidReceiveSettings( - EventBase[Literal["didReceiveSettings"]], - ContextualPayloadEventMixin[ - CardinalityDiscriminated[SingleActionSettingsPayload, MultiActionSettingsPayload] - ], - DeviceSpecificEventMixin, -): +class DidReceiveSettings(EventBase[Literal["didReceiveSettings"]], ContextualPayloadEventMixin[CardinalityDiscriminated[SingleActionSettingsPayload, MultiActionSettingsPayload]], DeviceSpecificEventMixin): # fmt: skip """Occurs when the settings associated with an action instance are requested, or when the the settings were updated by the property inspector.""" payload: CardinalityDiscriminated[SingleActionSettingsPayload, MultiActionSettingsPayload] diff --git a/streamdeck/models/events/title_parameters.py b/streamdeck/models/events/title_parameters.py index 76a7eee..4480c6c 100644 --- a/streamdeck/models/events/title_parameters.py +++ b/streamdeck/models/events/title_parameters.py @@ -50,11 +50,7 @@ class TitleParametersDidChangePayload( """Defines aesthetic properties that determine how the title should be rendered.""" -class TitleParametersDidChange( - EventBase[Literal["titleParametersDidChange"]], - ContextualPayloadEventMixin[TitleParametersDidChangePayload], - DeviceSpecificEventMixin, -): +class TitleParametersDidChange(EventBase[Literal["titleParametersDidChange"]], ContextualPayloadEventMixin[TitleParametersDidChangePayload], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user updates an action's title settings in the Stream Deck application.""" payload: TitleParametersDidChangePayload diff --git a/streamdeck/models/events/touch_tap.py b/streamdeck/models/events/touch_tap.py index d3faf34..82526d0 100644 --- a/streamdeck/models/events/touch_tap.py +++ b/streamdeck/models/events/touch_tap.py @@ -23,11 +23,7 @@ class TouchTapPayload(BasePayload[EncoderControllerType], CoordinatesPayloadMixi """Coordinates of where the touchscreen tap occurred, relative to the canvas of the action.""" -class TouchTap( - EventBase[Literal["touchTap"]], - ContextualPayloadEventMixin[TouchTapPayload], - DeviceSpecificEventMixin, -): +class TouchTap(EventBase[Literal["touchTap"]], ContextualPayloadEventMixin[TouchTapPayload], DeviceSpecificEventMixin): # fmt: skip """Occurs when the user taps the touchscreen (Stream Deck +).""" payload: TouchTapPayload diff --git a/streamdeck/models/events/visibility.py b/streamdeck/models/events/visibility.py index 97527e0..8c107f6 100644 --- a/streamdeck/models/events/visibility.py +++ b/streamdeck/models/events/visibility.py @@ -36,13 +36,7 @@ class MultiActionVisibilityPayload( ## Event models for WillAppear and WillDisappear events -class WillAppear( - EventBase[Literal["willAppear"]], - ContextualPayloadEventMixin[ - CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] - ], - DeviceSpecificEventMixin, -): +class WillAppear(EventBase[Literal["willAppear"]], ContextualPayloadEventMixin[CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload]], DeviceSpecificEventMixin): # fmt: skip """Occurs when an action appears on the Stream Deck due to the user navigating to another page, profile, folder, etc. This also occurs during startup if the action is on the "front page". @@ -52,13 +46,7 @@ class WillAppear( payload: CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] -class WillDisappear( - EventBase[Literal["willDisappear"]], - ContextualPayloadEventMixin[ - CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload] - ], - DeviceSpecificEventMixin, -): +class WillDisappear(EventBase[Literal["willDisappear"]], ContextualPayloadEventMixin[CardinalityDiscriminated[SingleActionVisibilityPayload, MultiActionVisibilityPayload]], DeviceSpecificEventMixin): # fmt: skip """Occurs when an action disappears from the Stream Deck due to the user navigating to another page, profile, folder, etc. An action refers to all types of actions, e.g. keys, dials, touchscreens, pedals, etc. From 861c45cb0bab650c99dbf93fa7c7035d8beca1ff Mon Sep 17 00:00:00 2001 From: strohganoff Date: Wed, 11 Nov 2026 15:29:43 -0700 Subject: [PATCH 3/3] Order generic payload model bases correctly --- streamdeck/models/events/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/streamdeck/models/events/common.py b/streamdeck/models/events/common.py index b613c97..292615e 100644 --- a/streamdeck/models/events/common.py +++ b/streamdeck/models/events/common.py @@ -1,6 +1,5 @@ from __future__ import annotations -from abc import ABC from typing import Annotated, Generic, Literal, NamedTuple, TypeAlias, Union from pydantic import Field @@ -55,7 +54,7 @@ class SettingsPayloadMixin: """Settings associated with a plugin or action instance.""" -class BasePayload(ConfiguredBaseModel, Generic[CT], ABC, SettingsPayloadMixin): +class BasePayload(ConfiguredBaseModel, SettingsPayloadMixin, Generic[CT]): """Base class for action payload models with controller and settings data.""" controller: CT