Add to_string helper on output DTMF frames

Mirrors the existing `from_string` classmethod and lets callers
turn a frame's `buttons` list back into a dial string like `"123#"`.
`__str__` and the Daily transport's native DTMF path reuse it.
This commit is contained in:
Aleix Conchillo Flaqué
2026-04-15 15:14:47 -07:00
parent 9fbe1bf2a3
commit f094ce80fb
2 changed files with 27 additions and 11 deletions

View File

@@ -745,11 +745,11 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
"""DTMF keypress output frame for transport queuing. """DTMF keypress output frame for transport queuing.
Parameters: Parameters:
buttons: Sequence of one or more DTMF keypad buttons to send. Use
:meth:`from_string` to build this from a string like ``"123#"``.
button: Convenience shortcut for sending a single DTMF keypad button: Convenience shortcut for sending a single DTMF keypad
entry. Equivalent to ``buttons=[button]``. If both ``buttons`` entry. Equivalent to ``buttons=[button]``. If both ``buttons``
and ``button`` are provided, ``buttons`` takes precedence. and ``button`` are provided, ``buttons`` takes precedence.
buttons: Sequence of one or more DTMF keypad buttons to send. Use
:meth:`from_string` to build this from a string like ``"123#"``.
""" """
button: Optional[KeypadEntry] = None button: Optional[KeypadEntry] = None
@@ -763,8 +763,7 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
raise ValueError(f"{self.__class__.__name__} requires `buttons` or `button` to be set") raise ValueError(f"{self.__class__.__name__} requires `buttons` or `button` to be set")
def __str__(self): def __str__(self):
buttons_str = "".join(b.value for b in self.buttons) if self.buttons else "" return f"{self.name}(buttons: {self.to_string()})"
return f"{self.name}(buttons: {buttons_str})"
@classmethod @classmethod
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFFrame": def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFFrame":
@@ -782,6 +781,16 @@ class OutputDTMFFrame(DTMFFrame, DataFrame):
""" """
return cls(buttons=[KeypadEntry(c) for c in buttons], **kwargs) return cls(buttons=[KeypadEntry(c) for c in buttons], **kwargs)
def to_string(self) -> str:
"""Return the frame's ``buttons`` as a dial string.
Returns:
A string such as ``"123#"`` formed by concatenating the values
of each :class:`~pipecat.audio.dtmf.types.KeypadEntry` in
``buttons``, or an empty string if ``buttons`` is not set.
"""
return "".join(b.value for b in self.buttons) if self.buttons else ""
# #
# System frames # System frames
@@ -1280,11 +1289,11 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
"""DTMF keypress output frame for immediate sending. """DTMF keypress output frame for immediate sending.
Parameters: Parameters:
buttons: Sequence of one or more DTMF keypad buttons to send. Use
:meth:`from_string` to build this from a string like ``"123#"``.
button: Convenience shortcut for sending a single DTMF keypad button: Convenience shortcut for sending a single DTMF keypad
entry. Equivalent to ``buttons=[button]``. If both ``buttons`` entry. Equivalent to ``buttons=[button]``. If both ``buttons``
and ``button`` are provided, ``buttons`` takes precedence. and ``button`` are provided, ``buttons`` takes precedence.
buttons: Sequence of one or more DTMF keypad buttons to send. Use
:meth:`from_string` to build this from a string like ``"123#"``.
""" """
button: Optional[KeypadEntry] = None button: Optional[KeypadEntry] = None
@@ -1298,8 +1307,7 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
raise ValueError(f"{self.__class__.__name__} requires `buttons` or `button` to be set") raise ValueError(f"{self.__class__.__name__} requires `buttons` or `button` to be set")
def __str__(self): def __str__(self):
buttons_str = "".join(b.value for b in self.buttons) if self.buttons else "" return f"{self.name}(buttons: {self.to_string()})"
return f"{self.name}(buttons: {buttons_str})"
@classmethod @classmethod
def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFUrgentFrame": def from_string(cls, buttons: str, **kwargs) -> "OutputDTMFUrgentFrame":
@@ -1317,6 +1325,16 @@ class OutputDTMFUrgentFrame(DTMFFrame, SystemFrame):
""" """
return cls(buttons=[KeypadEntry(c) for c in buttons], **kwargs) return cls(buttons=[KeypadEntry(c) for c in buttons], **kwargs)
def to_string(self) -> str:
"""Return the frame's ``buttons`` as a dial string.
Returns:
A string such as ``"123#"`` formed by concatenating the values
of each :class:`~pipecat.audio.dtmf.types.KeypadEntry` in
``buttons``, or an empty string if ``buttons`` is not set.
"""
return "".join(b.value for b in self.buttons) if self.buttons else ""
@dataclass @dataclass
class SpeechControlParamsFrame(SystemFrame): class SpeechControlParamsFrame(SystemFrame):

View File

@@ -2185,14 +2185,12 @@ class DailyOutputTransport(BaseOutputTransport):
if not frame.buttons: if not frame.buttons:
return return
settings: Dict[str, Any] = {"tones": "".join(b.value for b in frame.buttons)} settings: Dict[str, Any] = {"tones": frame.to_string()}
if isinstance(frame, (DailyOutputDTMFFrame, DailyOutputDTMFUrgentFrame)): if isinstance(frame, (DailyOutputDTMFFrame, DailyOutputDTMFUrgentFrame)):
if frame.session_id is not None: if frame.session_id is not None:
settings["sessionId"] = frame.session_id settings["sessionId"] = frame.session_id
if frame.digit_duration_ms is not None: if frame.digit_duration_ms is not None:
settings["digitDurationMs"] = frame.digit_duration_ms settings["digitDurationMs"] = frame.digit_duration_ms
elif frame.transport_destination is not None:
settings["sessionId"] = frame.transport_destination
await self._client.send_dtmf(settings) await self._client.send_dtmf(settings)