diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/component_dialog.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/component_dialog.py index b4c531b23..b297443df 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/component_dialog.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/component_dialog.py @@ -14,9 +14,24 @@ class ComponentDialog(Dialog): + """ + A :class:`Dialog` that is composed of other dialogs + + A component dialog has an inner :class:`DialogSet` :class:`DialogContext`, + which provides an inner dialog stack that is hidden from the parent dialog. + + :var persisted_dialog state: + :vartype persisted_dialog_state: str + """ persisted_dialog_state = "dialogs" def __init__(self, dialog_id: str): + """ + Initializes a new instance of the :class:`ComponentDialog` + + :param dialog_id: The ID to assign to the new dialog within the parent dialog set. + :type dialog_id: str + """ super(ComponentDialog, self).__init__(dialog_id) if dialog_id is None: @@ -30,6 +45,19 @@ def __init__(self, dialog_id: str): async def begin_dialog( self, dialog_context: DialogContext, options: object = None ) -> DialogTurnResult: + """ + Called when the dialog is started and pushed onto the parent's dialog stack. + + If the task is successful, the result indicates whether the dialog is still + active after the turn has been processed by the dialog. + + :param dialog_context: The :class:`DialogContext` for the current turn of the conversation. + :type dialog_context: :class:`DialogContext` + :param options: Optional, initial information to pass to the dialog. + :type options: object + :return: Signals the end of the turn + :rtype: :class:`Dialog.end_of_turn` + """ if dialog_context is None: raise TypeError("ComponentDialog.begin_dialog(): outer_dc cannot be None.") @@ -49,6 +77,27 @@ async def begin_dialog( return Dialog.end_of_turn async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResult: + """ + Called when the dialog is continued, where it is the active dialog and the + user replies with a new activity. + + .. note:: + If the task is successful, the result indicates whether the dialog is still + active after the turn has been processed by the dialog. The result may also + contain a return value. + + If this method is *not* overriden the component dialog calls the + :meth:`DialogContext.continue_dialog` method on it's inner dialog + context. If the inner dialog stack is empty, the component dialog ends, + and if a :class:`DialogTurnResult.result` is available, the component dialog + uses that as it's return value. + + + :param dialog_context: The parent :class:`DialogContext` for the current turn of the conversation. + :type dialog_context: :class:`DialogContext` + :return: Signals the end of the turn + :rtype: :class:`Dialog.end_of_turn` + """ if dialog_context is None: raise TypeError("ComponentDialog.begin_dialog(): outer_dc cannot be None.") # Continue execution of inner dialog. @@ -65,17 +114,51 @@ async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResu async def resume_dialog( self, dialog_context: DialogContext, reason: DialogReason, result: object = None ) -> DialogTurnResult: - # Containers are typically leaf nodes on the stack but the dev is free to push other dialogs - # on top of the stack which will result in the container receiving an unexpected call to - # resume_dialog() when the pushed on dialog ends. - # To avoid the container prematurely ending we need to implement this method and simply - # ask our inner dialog stack to re-prompt. + """ + Called when a child dialog on the parent's dialog stack completed this turn, returning + control to this dialog component. + + .. note:: + Containers are typically leaf nodes on the stack but the dev is free to push other dialogs + on top of the stack which will result in the container receiving an unexpected call to + :meth:resume_dialog() when the pushed on dialog ends. + To avoid the container prematurely ending we need to implement this method and simply + ask our inner dialog stack to re-prompt. + + If the task is successful, the result indicates whether this dialog is still + active after this dialog turn has been processed. + + Generally, the child dialog was started with a call to :meth:`def async begin_dialog()` + in the parent's context. However, if the :meth:`DialogContext.replace_dialog()` method is + is called, the logical child dialog may be different than the original. + + If this method is *not* overridden, the dialog automatically calls its + :meth:`asyn def reprompt_dialog()` when the user replies. + + :param dialog_context: The :class:`DialogContext` for the current turn of the conversation. + :type dialog_context: :class:`DialogContext` + :param reason: Reason why the dialog resumed. + :type reason: :class:`DialogReason` + :param result: Optional, value returned from the dialog that was called. The type of the value returned is dependent on the child dialog. + :type result: object + :return: Signals the end of the turn + :rtype: :class:`Dialog.end_of_turn` + """ + await self.reprompt_dialog(dialog_context.context, dialog_context.active_dialog) return Dialog.end_of_turn async def reprompt_dialog( self, context: TurnContext, instance: DialogInstance ) -> None: + """ + Called when the dialog should re-prompt the user for input. + + :param context: The context object for this turn. + :type context: :class:`TurnContext` + :param instance: State information for this dialog. + :type instance: :class:`DialogInstance` + """ # Delegate to inner dialog. dialog_state = instance.state[self.persisted_dialog_state] inner_dc = DialogContext(self._dialogs, context, dialog_state) @@ -87,7 +170,17 @@ async def reprompt_dialog( async def end_dialog( self, context: TurnContext, instance: DialogInstance, reason: DialogReason ) -> None: - # Forward cancel to inner dialogs + """ + Called when the dialog is ending. + + :param context: The context object for this turn. + :type context: :class:`TurnContext` + :param instance: State information associated with the instance of this component dialog on its parent's dialog stack. + :type instance: :class:`DialogInstance` + :param reason: Reason why the dialog ended. + :type reason: :class:`DialogReason` + """ + # Forward cancel to inner dialog if reason == DialogReason.CancelCalled: dialog_state = instance.state[self.persisted_dialog_state] inner_dc = DialogContext(self._dialogs, context, dialog_state) @@ -96,10 +189,12 @@ async def end_dialog( def add_dialog(self, dialog: Dialog) -> object: """ - Adds a dialog to the component dialog. - Adding a new dialog will inherit the BotTelemetryClient of the ComponentDialog. + Adds a :class:`Dialog` to the component dialog and returns the updated component. + Adding a new dialog will inherit the :class:`BotTelemetryClient` of the :class:`ComponentDialog`. + :param dialog: The dialog to add. - :return: The updated ComponentDialog + :return: The updated :class:`ComponentDialog` + :rtype: :class:`ComponentDialog` """ self._dialogs.add(dialog) if not self.initial_dialog_id: @@ -109,15 +204,36 @@ def add_dialog(self, dialog: Dialog) -> object: def find_dialog(self, dialog_id: str) -> Dialog: """ Finds a dialog by ID. - Adding a new dialog will inherit the BotTelemetryClient of the ComponentDialog. + Adding a new dialog will inherit the :class:`BotTelemetryClient` of the :class:`ComponentDialog`. + :param dialog_id: The dialog to add. :return: The dialog; or None if there is not a match for the ID. + :rtype: :class:Dialog """ return self._dialogs.find(dialog_id) async def on_begin_dialog( self, inner_dc: DialogContext, options: object ) -> DialogTurnResult: + """ + Called when the dialog is started and pushed onto the parent's dialog stack. + + .. note:: + If the task is successful, the result indicates whether the dialog is still + active after the turn has been processed by the dialog. + + By default, this calls the :meth:`Dialog.begin_dialog()` method of the component + dialog's initial dialog. + + Override this method in a derived class to implement interrupt logic. + + :param inner_dc: The inner :class:`DialogContext` for the current turn of conversation. + :type inner_dc: :class:`DialogContext` + :param options: Optional, initial information to pass to the dialog. + :type options: object + :return: ? + :rtype: ? + """ return await inner_dc.begin_dialog(self.initial_dialog_id, options) async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult: @@ -126,14 +242,53 @@ async def on_continue_dialog(self, inner_dc: DialogContext) -> DialogTurnResult: async def on_end_dialog( # pylint: disable=unused-argument self, context: TurnContext, instance: DialogInstance, reason: DialogReason ) -> None: + """ + Ends the component dialog in its parent's context. + + :param turn_context: The :class:`TurnContext` for the current turn of the conversation. + :type turn_context: :class:`TurnContext` + :param instance: State information associated with the instance of this component dialog on its parent's dialog stack. + :type instance: :class:`DialogInstance` + :param reason: Reason why the dialog ended. + :type reason: :class:`DialogReason` + """ return async def on_reprompt_dialog( # pylint: disable=unused-argument self, turn_context: TurnContext, instance: DialogInstance ) -> None: + """ + :param turn_context: The :class:`TurnContext` for the current turn of the conversation. + :type turn_context: :class:`DialogInstance` + :param instance: State information associated with the instance of this component dialog on its parent's dialog stack. + :type instance: :class:`DialogInstance` + """ return async def end_component( self, outer_dc: DialogContext, result: object # pylint: disable=unused-argument ) -> DialogTurnResult: + """ + Ends the component dialog in its parent's context. + + .. note:: + If the task is successful, the result indicates that the dialog ended after the + turn was processed by the dialog. + + In general, the parent context is the dialog or bot turn handler that started the dialog. + If the parent is a dialog, the stack calls the parent's :meth:`Dialog.resume_dialog()` method + to return a result to the parent dialog. If the parent dialog does not implement + :meth:`Dialog.resume_dialog()`, then the parent will end, too, and the result is passed to the next + parent context, if one exists. + + The returned :class:`DialogTurnResult`contains the return value in its + :class:`DialogTurnResult.result` property. + + :param outer_dc: The parent class:`DialogContext` for the current turn of conversation. + :type outer_dc: class:`DialogContext` + :param result: Optional, value to return from the dialog component to the parent context. + :type result: object + :return: Value to return. + :rtype: :class:`DialogTurnResult.result` + """ return await outer_dc.end_dialog(result) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/activity_prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/activity_prompt.py index 5930441e1..0cfa4739f 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/activity_prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/activity_prompt.py @@ -24,9 +24,15 @@ class ActivityPrompt(Dialog, ABC): """ Waits for an activity to be received. - This prompt requires a validator be passed in and is useful when waiting for non-message - activities like an event to be received. The validator can ignore received events until the - expected activity is received. + ..remarks: + This prompt requires a validator be passed in and is useful when waiting for non-message + activities like an event to be received. The validator can ignore received events until the + expected activity is received. + + :var persisted_options: ? + :typevar persisted_options: str + :var persisted_state: ? + :vartype persisted_state: str """ persisted_options = "options" @@ -36,13 +42,12 @@ def __init__( self, dialog_id: str, validator: Callable[[PromptValidatorContext], bool] ): """ - Initializes a new instance of the ActivityPrompt class. + Initializes a new instance of the :class:`ActivityPrompt` class. - Parameters: - ---------- - dialog_id (str): Unique ID of the dialog within its parent DialogSet or ComponentDialog. - - validator: Validator that will be called each time a new activity is received. + :param dialog_id: Unique ID of the dialog within its parent :class:`DialogSet` or :class:`ComponentDialog`. + :type dialog_id: str + :param validator: Validator that will be called each time a new activity is received. + :type validator: Callable[[PromptValidatorContext], bool] """ Dialog.__init__(self, dialog_id) @@ -53,6 +58,16 @@ def __init__( async def begin_dialog( self, dialog_context: DialogContext, options: PromptOptions = None ) -> DialogTurnResult: + """ + Called when a prompt dialog is pushed onto the dialog stack and is being activated. + + :param dialog_context: The dialog context for the current turn of the conversation. + :type dialog_context: :class:`DialogContext` + :param options: Optional, additional information to pass to the prompt being started. + :type options: :class:`PromptOptions` + :return Dialog.end_of_turn: + :rtype Dialog.end_of_turn: :class:`Dialog.DialogTurnResult` + """ if not dialog_context: raise TypeError("ActivityPrompt.begin_dialog(): dc cannot be None.") if not isinstance(options, PromptOptions): @@ -84,6 +99,14 @@ async def begin_dialog( async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResult: if not dialog_context: + """ + Called when a prompt dialog is the active dialog and the user replied with a new activity. + + :param dialog_context: The dialog context for the current turn of the conversation. + :type dialog_context: :class:`DialogContext` + :return Dialog.end_of_turn: + :rtype Dialog.end_of_turn: :class:`Dialog.DialogTurnResult` + """ raise TypeError( "ActivityPrompt.continue_dialog(): DialogContext cannot be None." ) @@ -130,11 +153,19 @@ async def resume_dialog( # pylint: disable=unused-argument self, dialog_context: DialogContext, reason: DialogReason, result: object = None ): """ - Prompts are typically leaf nodes on the stack but the dev is free to push other dialogs - on top of the stack which will result in the prompt receiving an unexpected call to - resume_dialog() when the pushed on dialog ends. - To avoid the prompt prematurely ending, we need to implement this method and - simply re-prompt the user + Called when a prompt dialog resumes being the active dialog on the dialog stack, such as when the previous active dialog on the stack completes. + ..remarks: + Prompts are typically leaf nodes on the stack but the dev is free to push other dialogs + on top of the stack which will result in the prompt receiving an unexpected call to + resume_dialog() when the pushed on dialog ends. + To avoid the prompt prematurely ending, we need to implement this method and + simply re-prompt the user. + :param dialog_context: The dialog context for the current turn of the conversation + :type dialog_context: :class:`DialogContext` + :param reason: An enum indicating why the dialog resumed. + :type reason: :class:`DialogReason` + :param result: >Optional, value returned from the previous dialog on the stack. The type of the value returned is dependent on the previous dialog. + :type result: object """ await self.reprompt_dialog(dialog_context.context, dialog_context.active_dialog) @@ -155,15 +186,14 @@ async def on_prompt( """ Called anytime the derived class should send the user a prompt. - Parameters: - ---------- - context: Context for the current turn of conversation with the user. - - state: Additional state being persisted for the prompt. - - options: Options that the prompt started with in the call to `DialogContext.prompt()`. - - isRetry: If `true` the users response wasn't recognized and the re-prompt should be sent. + :param dialog_context: The dialog context for the current turn of the conversation + :type dialog_context: :class:`DialogContext` + :param state: Additional state being persisted for the prompt. + :type state: Dict[str, dict] + :param options: Options that the prompt started with in the call to `DialogContext.prompt()`. + :type options: :class:`PromptOptions` + :param isRetry: If `true` the users response wasn't recognized and the re-prompt should be sent. + :type isRetry: bool """ if is_retry and options.retry_prompt: options.retry_prompt.input_hint = InputHints.expecting_input @@ -175,7 +205,18 @@ async def on_prompt( async def on_recognize( # pylint: disable=unused-argument self, context: TurnContext, state: Dict[str, object], options: PromptOptions ) -> PromptRecognizerResult: - + """ + When overridden in a derived class, attempts to recognize the incoming activity. + + :param context: Context for the current turn of conversation with the user. + :type context: :class:`TurnContext` + :param state: Contains state for the current instance of the prompt on the dialog stack. + :type state: Dict[str, object] + :param options: A prompt options object + :type options: :class:`PromptOptions` + :return result: constructed from the options initially provided in the call to `async def on_prompt()` + :rtype result: :class:`PromptRecognizerResult` + """ result = PromptRecognizerResult() result.succeeded = (True,) result.value = context.activity