• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microsoft / botbuilder-python / 391598

08 Jul 2024 05:48PM UTC coverage: 66.983% (+0.02%) from 66.964%
391598

push

python-ci

web-flow
Fix for ConfigurationServiceClientCredentialFactory to allow auth disabled scenario (#2135)

Co-authored-by: Tracy Boehrer <trboehre@microsoft.com>

9166 of 13684 relevant lines covered (66.98%)

2.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.66
/libraries/botbuilder-dialogs/botbuilder/dialogs/dialog_context.py
1
# Copyright (c) Microsoft Corporation. All rights reserved.
2
# Licensed under the MIT License.
3

4
from typing import List, Optional
4✔
5

6
from botbuilder.core.turn_context import TurnContext
4✔
7
from botbuilder.dialogs.memory import DialogStateManager
4✔
8

9
from .dialog_event import DialogEvent
4✔
10
from .dialog_events import DialogEvents
4✔
11
from .dialog_set import DialogSet
4✔
12
from .dialog_state import DialogState
4✔
13
from .dialog_turn_status import DialogTurnStatus
4✔
14
from .dialog_turn_result import DialogTurnResult
4✔
15
from .dialog_reason import DialogReason
4✔
16
from .dialog_instance import DialogInstance
4✔
17
from .dialog import Dialog
4✔
18

19

20
class DialogContext:
4✔
21
    def __init__(
4✔
22
        self, dialog_set: DialogSet, turn_context: TurnContext, state: DialogState
23
    ):
24
        if dialog_set is None:
4✔
25
            raise TypeError("DialogContext(): dialog_set cannot be None.")
×
26
        # TODO: Circular dependency with dialog_set: Check type.
27
        if turn_context is None:
4✔
28
            raise TypeError("DialogContext(): turn_context cannot be None.")
×
29
        self._turn_context = turn_context
4✔
30
        self._dialogs = dialog_set
4✔
31
        self._stack = state.dialog_stack
4✔
32
        self.services = {}
4✔
33
        self.parent: DialogContext = None
4✔
34
        self.state = DialogStateManager(self)
4✔
35

36
    @property
4✔
37
    def dialogs(self) -> DialogSet:
4✔
38
        """Gets the set of dialogs that can be called from this context.
39

40
        :param:
41
        :return DialogSet:
42
        """
43
        return self._dialogs
4✔
44

45
    @property
4✔
46
    def context(self) -> TurnContext:
4✔
47
        """Gets the context for the current turn of conversation.
48

49
        :param:
50
        :return TurnContext:
51
        """
52
        return self._turn_context
4✔
53

54
    @property
4✔
55
    def stack(self) -> List:
4✔
56
        """Gets the current dialog stack.
57

58
        :param:
59
        :return list:
60
        """
61
        return self._stack
4✔
62

63
    @property
4✔
64
    def active_dialog(self):
4✔
65
        """Return the container link in the database.
66

67
        :param:
68
        :return:
69
        """
70
        if self._stack:
4✔
71
            return self._stack[0]
4✔
72
        return None
4✔
73

74
    @property
4✔
75
    def child(self) -> Optional["DialogContext"]:
4✔
76
        """Return the container link in the database.
77

78
        :param:
79
        :return DialogContext:
80
        """
81
        # pylint: disable=import-outside-toplevel
82
        instance = self.active_dialog
4✔
83

84
        if instance:
4✔
85
            dialog = self.find_dialog_sync(instance.id)
4✔
86

87
            # This import prevents circular dependency issues
88
            from .dialog_container import DialogContainer
4✔
89

90
            if isinstance(dialog, DialogContainer):
4✔
91
                return dialog.create_child_context(self)
4✔
92

93
        return None
4✔
94

95
    async def begin_dialog(self, dialog_id: str, options: object = None):
4✔
96
        """
97
        Pushes a new dialog onto the dialog stack.
98
        :param dialog_id: ID of the dialog to start
99
        :param options: (Optional) additional argument(s) to pass to the dialog being started.
100
        """
101
        try:
4✔
102
            if not dialog_id:
4✔
103
                raise TypeError("Dialog(): dialog_id cannot be None.")
×
104
            # Look up dialog
105
            dialog = await self.find_dialog(dialog_id)
4✔
106
            if dialog is None:
4✔
107
                raise Exception(
4✔
108
                    "'DialogContext.begin_dialog(): A dialog with an id of '%s' wasn't found."
109
                    " The dialog must be included in the current or parent DialogSet."
110
                    " For example, if subclassing a ComponentDialog you can call add_dialog() within your constructor."
111
                    % dialog_id
112
                )
113
            # Push new instance onto stack
114
            instance = DialogInstance()
4✔
115
            instance.id = dialog_id
4✔
116
            instance.state = {}
4✔
117

118
            self._stack.insert(0, (instance))
4✔
119

120
            # Call dialog's begin_dialog() method
121
            return await dialog.begin_dialog(self, options)
4✔
122
        except Exception as err:
4✔
123
            self.__set_exception_context_data(err)
4✔
124
            raise
4✔
125

126
    # TODO: Fix options: PromptOptions instead of object
127
    async def prompt(self, dialog_id: str, options) -> DialogTurnResult:
4✔
128
        """
129
        Helper function to simplify formatting the options for calling a prompt dialog. This helper will
130
        take a `PromptOptions` argument and then call.
131
        :param dialog_id: ID of the prompt to start.
132
        :param options: Contains a Prompt, potentially a RetryPrompt and if using ChoicePrompt, Choices.
133
        :return:
134
        """
135
        try:
4✔
136
            if not dialog_id:
4✔
137
                raise TypeError("DialogContext.prompt(): dialogId cannot be None.")
×
138

139
            if not options:
4✔
140
                raise TypeError("DialogContext.prompt(): options cannot be None.")
×
141

142
            return await self.begin_dialog(dialog_id, options)
4✔
143
        except Exception as err:
4✔
144
            self.__set_exception_context_data(err)
4✔
145
            raise
4✔
146

147
    async def continue_dialog(self):
4✔
148
        """
149
        Continues execution of the active dialog, if there is one, by passing the context object to
150
        its `Dialog.continue_dialog()` method. You can check `turn_context.responded` after the call completes
151
        to determine if a dialog was run and a reply was sent to the user.
152
        :return:
153
        """
154
        try:
4✔
155
            # Check for a dialog on the stack
156
            if self.active_dialog is not None:
4✔
157
                # Look up dialog
158
                dialog = await self.find_dialog(self.active_dialog.id)
4✔
159
                if not dialog:
4✔
160
                    raise Exception(
×
161
                        "DialogContext.continue_dialog(): Can't continue dialog. "
162
                        "A dialog with an id of '%s' wasn't found."
163
                        % self.active_dialog.id
164
                    )
165

166
                # Continue execution of dialog
167
                return await dialog.continue_dialog(self)
4✔
168

169
            return DialogTurnResult(DialogTurnStatus.Empty)
4✔
170
        except Exception as err:
×
171
            self.__set_exception_context_data(err)
×
172
            raise
×
173

174
    # TODO: instance is DialogInstance
175
    async def end_dialog(self, result: object = None):
4✔
176
        """
177
        Ends a dialog by popping it off the stack and returns an optional result to the dialog's
178
        parent. The parent dialog is the dialog that started the dialog being ended via a call to
179
        either "begin_dialog" or "prompt".
180
        The parent dialog will have its `Dialog.resume_dialog()` method invoked with any returned
181
        result. If the parent dialog hasn't implemented a `resume_dialog()` method then it will be
182
        automatically ended as well and the result passed to its parent. If there are no more
183
        parent dialogs on the stack then processing of the turn will end.
184
        :param result: (Optional) result to pass to the parent dialogs.
185
        :return:
186
        """
187
        try:
4✔
188
            await self.end_active_dialog(DialogReason.EndCalled)
4✔
189

190
            # Resume previous dialog
191
            if self.active_dialog is not None:
4✔
192
                # Look up dialog
193
                dialog = await self.find_dialog(self.active_dialog.id)
4✔
194
                if not dialog:
4✔
195
                    raise Exception(
×
196
                        "DialogContext.EndDialogAsync(): Can't resume previous dialog."
197
                        " A dialog with an id of '%s' wasn't found."
198
                        % self.active_dialog.id
199
                    )
200

201
                # Return result to previous dialog
202
                return await dialog.resume_dialog(self, DialogReason.EndCalled, result)
4✔
203

204
            return DialogTurnResult(DialogTurnStatus.Complete, result)
4✔
205
        except Exception as err:
×
206
            self.__set_exception_context_data(err)
×
207
            raise
×
208

209
    async def cancel_all_dialogs(
4✔
210
        self,
211
        cancel_parents: bool = None,
212
        event_name: str = None,
213
        event_value: object = None,
214
    ):
215
        """
216
        Deletes any existing dialog stack thus cancelling all dialogs on the stack.
217
        :param cancel_parents:
218
        :param event_name:
219
        :param event_value:
220
        :return:
221
        """
222
        try:
4✔
223
            event_name = event_name or DialogEvents.cancel_dialog
4✔
224
            if self.stack or self.parent:
4✔
225
                # Cancel all local and parent dialogs while checking for interception
226
                notify = False
4✔
227
                dialog_context = self
4✔
228

229
                while dialog_context:
4✔
230
                    if dialog_context.stack:
4✔
231
                        # Check to see if the dialog wants to handle the event
232
                        if notify:
4✔
233
                            event_handled = await dialog_context.emit_event(
4✔
234
                                event_name,
235
                                event_value,
236
                                bubble=False,
237
                                from_leaf=False,
238
                            )
239

240
                            if event_handled:
4✔
241
                                break
×
242

243
                        # End the active dialog
244
                        await dialog_context.end_active_dialog(
4✔
245
                            DialogReason.CancelCalled
246
                        )
247
                    else:
248
                        dialog_context = (
4✔
249
                            dialog_context.parent if cancel_parents else None
250
                        )
251

252
                    notify = True
4✔
253

254
                return DialogTurnResult(DialogTurnStatus.Cancelled)
4✔
255

256
            # Stack was empty and no parent
257
            return DialogTurnResult(DialogTurnStatus.Empty)
×
258
        except Exception as err:
×
259
            self.__set_exception_context_data(err)
×
260
            raise
×
261

262
    async def find_dialog(self, dialog_id: str) -> Dialog:
4✔
263
        """
264
        If the dialog cannot be found within the current `DialogSet`, the parent `DialogContext`
265
        will be searched if there is one.
266
        :param dialog_id: ID of the dialog to search for.
267
        :return:
268
        """
269
        try:
4✔
270
            dialog = await self.dialogs.find(dialog_id)
4✔
271

272
            if dialog is None and self.parent is not None:
4✔
273
                dialog = await self.parent.find_dialog(dialog_id)
×
274
            return dialog
4✔
275
        except Exception as err:
×
276
            self.__set_exception_context_data(err)
×
277
            raise
×
278

279
    def find_dialog_sync(self, dialog_id: str) -> Dialog:
4✔
280
        """
281
        If the dialog cannot be found within the current `DialogSet`, the parent `DialogContext`
282
        will be searched if there is one.
283
        :param dialog_id: ID of the dialog to search for.
284
        :return:
285
        """
286
        dialog = self.dialogs.find_dialog(dialog_id)
4✔
287

288
        if dialog is None and self.parent is not None:
4✔
289
            dialog = self.parent.find_dialog_sync(dialog_id)
×
290
        return dialog
4✔
291

292
    async def replace_dialog(
4✔
293
        self, dialog_id: str, options: object = None
294
    ) -> DialogTurnResult:
295
        """
296
        Ends the active dialog and starts a new dialog in its place. This is particularly useful
297
        for creating loops or redirecting to another dialog.
298
        :param dialog_id: ID of the dialog to search for.
299
        :param options: (Optional) additional argument(s) to pass to the new dialog.
300
        :return:
301
        """
302
        try:
4✔
303
            # End the current dialog and giving the reason.
304
            await self.end_active_dialog(DialogReason.ReplaceCalled)
4✔
305

306
            # Start replacement dialog
307
            return await self.begin_dialog(dialog_id, options)
4✔
308
        except Exception as err:
4✔
309
            self.__set_exception_context_data(err)
4✔
310
            raise
4✔
311

312
    async def reprompt_dialog(self):
4✔
313
        """
314
        Calls reprompt on the currently active dialog, if there is one. Used with Prompts that have a reprompt behavior.
315
        :return:
316
        """
317
        try:
4✔
318
            # Check for a dialog on the stack
319
            if self.active_dialog is not None:
4✔
320
                # Look up dialog
321
                dialog = await self.find_dialog(self.active_dialog.id)
4✔
322
                if not dialog:
4✔
323
                    raise Exception(
×
324
                        "DialogSet.reprompt_dialog(): Can't find A dialog with an id of '%s'."
325
                        % self.active_dialog.id
326
                    )
327

328
                # Ask dialog to re-prompt if supported
329
                await dialog.reprompt_dialog(self.context, self.active_dialog)
4✔
330
        except Exception as err:
×
331
            self.__set_exception_context_data(err)
×
332
            raise
×
333

334
    async def end_active_dialog(self, reason: DialogReason):
4✔
335
        instance = self.active_dialog
4✔
336
        if instance is not None:
4✔
337
            # Look up dialog
338
            dialog = await self.find_dialog(instance.id)
4✔
339
            if dialog is not None:
4✔
340
                # Notify dialog of end
341
                await dialog.end_dialog(self.context, instance, reason)
4✔
342

343
            # Pop dialog off stack
344
            self._stack.pop(0)
4✔
345

346
    async def emit_event(
4✔
347
        self,
348
        name: str,
349
        value: object = None,
350
        bubble: bool = True,
351
        from_leaf: bool = False,
352
    ) -> bool:
353
        """
354
        Searches for a dialog with a given ID.
355
        Emits a named event for the current dialog, or someone who started it, to handle.
356
        :param name: Name of the event to raise.
357
        :param value: Value to send along with the event.
358
        :param bubble: Flag to control whether the event should be bubbled to its parent if not handled locally.
359
        Defaults to a value of `True`.
360
        :param from_leaf: Whether the event is emitted from a leaf node.
361
        :param cancellationToken: The cancellation token.
362
        :return: True if the event was handled.
363
        """
364
        try:
4✔
365
            # Initialize event
366
            dialog_event = DialogEvent(
4✔
367
                bubble=bubble,
368
                name=name,
369
                value=value,
370
            )
371

372
            dialog_context = self
4✔
373

374
            # Find starting dialog
375
            if from_leaf:
4✔
376
                while True:
377
                    child_dc = dialog_context.child
×
378

379
                    if child_dc:
×
380
                        dialog_context = child_dc
×
381
                    else:
382
                        break
×
383

384
            # Dispatch to active dialog first
385
            instance = dialog_context.active_dialog
4✔
386

387
            if instance:
4✔
388
                dialog = await dialog_context.find_dialog(instance.id)
4✔
389

390
                if dialog:
4✔
391
                    return await dialog.on_dialog_event(dialog_context, dialog_event)
4✔
392

393
            return False
×
394
        except Exception as err:
×
395
            self.__set_exception_context_data(err)
×
396
            raise
×
397

398
    def __set_exception_context_data(self, exception: Exception):
4✔
399
        if not hasattr(exception, "data"):
4✔
400
            exception.data = {}
4✔
401

402
        if not type(self).__name__ in exception.data:
4✔
403
            stack = []
4✔
404
            current_dc = self
4✔
405

406
            while current_dc is not None:
4✔
407
                stack = stack + [x.id for x in current_dc.stack]
4✔
408
                current_dc = current_dc.parent
4✔
409

410
            exception.data[type(self).__name__] = {
4✔
411
                "active_dialog": (
412
                    None if self.active_dialog is None else self.active_dialog.id
413
                ),
414
                "parent": None if self.parent is None else self.parent.active_dialog.id,
415
                "stack": self.stack,
416
            }
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc