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

microsoft / botbuilder-python / 409676

28 May 2025 05:17PM UTC coverage: 67.239% (-0.008%) from 67.247%
409676

push

python-ci

web-flow
Teams SSO and OAuth fixes (#2226)

* Fixed Teams SSO & OAuth

* Formatting

---------

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

10 of 21 new or added lines in 8 files covered. (47.62%)

1 existing line in 1 file now uncovered.

9205 of 13690 relevant lines covered (67.24%)

2.68 hits per line

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

72.39
/libraries/botbuilder-core/botbuilder/core/activity_handler.py
1
# Copyright (c) Microsoft Corporation. All rights reserved.
2
# Licensed under the MIT License.
3
from http import HTTPStatus
4✔
4
from typing import List, Union
4✔
5

6
from botbuilder.schema import (
4✔
7
    Activity,
8
    ActivityTypes,
9
    AdaptiveCardInvokeResponse,
10
    AdaptiveCardInvokeValue,
11
    ChannelAccount,
12
    InvokeResponse,
13
    MessageReaction,
14
    SignInConstants,
15
)
16

17
from .bot import Bot
4✔
18
from .serializer_helper import serializer_helper
4✔
19
from .bot_framework_adapter import BotFrameworkAdapter
4✔
20
from .turn_context import TurnContext
4✔
21

22

23
class ActivityHandler(Bot):
4✔
24
    """
25
    Handles activities and should be subclassed.
26

27
    .. remarks::
28
        Derive from this class to handle particular activity types.
29
        Yon can add pre and post processing of activities by calling the base class
30
        in the derived class.
31
    """
32

33
    async def on_turn(
4✔
34
        self, turn_context: TurnContext
35
    ):  # pylint: disable=arguments-differ
36
        """
37
        Called by the adapter (for example, :class:`BotFrameworkAdapter`) at runtime
38
        in order to process an inbound :class:`botbuilder.schema.Activity`.
39

40
        :param turn_context: The context object for this turn
41
        :type turn_context: :class:`botbuilder.core.TurnContext`
42

43
        :returns: A task that represents the work queued to execute
44

45
        .. remarks::
46
            It calls other methods in this class based on the type of the activity to
47
            process, which allows a derived class to provide type-specific logic in a controlled way.
48
            In a derived class, override this method to add logic that applies to all activity types.
49
            Also
50
            - Add logic to apply before the type-specific logic and before calling :meth:`on_turn()`.
51
            - Add logic to apply after the type-specific logic after calling :meth:`on_turn()`.
52
        """
53
        if turn_context is None:
4✔
54
            raise TypeError("ActivityHandler.on_turn(): turn_context cannot be None.")
×
55

56
        if hasattr(turn_context, "activity") and turn_context.activity is None:
4✔
57
            raise TypeError(
×
58
                "ActivityHandler.on_turn(): turn_context must have a non-None activity."
59
            )
60

61
        if (
4✔
62
            hasattr(turn_context.activity, "type")
63
            and turn_context.activity.type is None
64
        ):
65
            raise TypeError(
×
66
                "ActivityHandler.on_turn(): turn_context activity must have a non-None type."
67
            )
68

69
        if turn_context.activity.type == ActivityTypes.message:
4✔
70
            await self.on_message_activity(turn_context)
4✔
71
        elif turn_context.activity.type == ActivityTypes.message_update:
4✔
72
            await self.on_message_update_activity(turn_context)
4✔
73
        elif turn_context.activity.type == ActivityTypes.message_delete:
4✔
74
            await self.on_message_delete_activity(turn_context)
4✔
75
        elif turn_context.activity.type == ActivityTypes.conversation_update:
4✔
76
            await self.on_conversation_update_activity(turn_context)
4✔
77
        elif turn_context.activity.type == ActivityTypes.message_reaction:
4✔
78
            await self.on_message_reaction_activity(turn_context)
4✔
79
        elif turn_context.activity.type == ActivityTypes.event:
4✔
80
            await self.on_event_activity(turn_context)
4✔
81
        elif turn_context.activity.type == ActivityTypes.invoke:
4✔
82
            invoke_response = await self.on_invoke_activity(turn_context)
4✔
83

84
            # If OnInvokeActivityAsync has already sent an InvokeResponse, do not send another one.
85
            if invoke_response and not turn_context.turn_state.get(
4✔
86
                BotFrameworkAdapter._INVOKE_RESPONSE_KEY  # pylint: disable=protected-access
87
            ):
88
                await turn_context.send_activity(
4✔
89
                    Activity(value=invoke_response, type=ActivityTypes.invoke_response)
90
                )
91
        elif turn_context.activity.type == ActivityTypes.end_of_conversation:
4✔
92
            await self.on_end_of_conversation_activity(turn_context)
4✔
93
        elif turn_context.activity.type == ActivityTypes.typing:
4✔
94
            await self.on_typing_activity(turn_context)
4✔
95
        elif turn_context.activity.type == ActivityTypes.installation_update:
4✔
96
            await self.on_installation_update(turn_context)
4✔
97
        else:
98
            await self.on_unrecognized_activity_type(turn_context)
4✔
99

100
    async def on_message_activity(  # pylint: disable=unused-argument
4✔
101
        self, turn_context: TurnContext
102
    ):
103
        """
104
        Override this method in a derived class to provide logic specific to activities,
105
        such as the conversational logic.
106

107
        :param turn_context: The context object for this turn
108
        :type turn_context: :class:`botbuilder.core.TurnContext`
109

110
        :returns: A task that represents the work queued to execute
111
        """
112
        return
4✔
113

114
    async def on_message_update_activity(  # pylint: disable=unused-argument
4✔
115
        self, turn_context: TurnContext
116
    ):
117
        """
118
        Override this method in a derived class to provide logic specific to activities,
119
        such as the conversational logic.
120

121
        :param turn_context: The context object for this turn
122
        :type turn_context: :class:`botbuilder.core.TurnContext`
123

124
        :returns: A task that represents the work queued to execute
125
        """
126
        return
4✔
127

128
    async def on_message_delete_activity(  # pylint: disable=unused-argument
4✔
129
        self, turn_context: TurnContext
130
    ):
131
        """
132
        Override this method in a derived class to provide logic specific to activities,
133
        such as the conversational logic.
134

135
        :param turn_context: The context object for this turn
136
        :type turn_context: :class:`botbuilder.core.TurnContext`
137

138
        :returns: A task that represents the work queued to execute
139
        """
140
        return
4✔
141

142
    async def on_conversation_update_activity(self, turn_context: TurnContext):
4✔
143
        """
144
        Invoked when a conversation update activity is received from the channel when the base behavior of
145
        :meth:`on_turn()` is used.
146

147
        :param turn_context: The context object for this turn
148
        :type turn_context: :class:`botbuilder.core.TurnContext`
149

150
        :returns: A task that represents the work queued to execute
151

152
        .. remarks::
153
            When the :meth:`on_turn()` method receives a conversation update activity, it calls this
154
            method.
155
            Also
156
            - If the conversation update activity indicates that members other than the bot joined the conversation,
157
            it calls the  :meth:`on_members_added_activity()` method.
158
            - If the conversation update activity indicates that members other than the bot left the conversation,
159
            it calls the  :meth:`on_members_removed_activity()`  method.
160
            - In a derived class, override this method to add logic that applies to all conversation update activities.
161
            Add logic to apply before the member added or removed logic before the call to this base class method.
162
        """
163
        if (
×
164
            turn_context.activity.members_added is not None
165
            and turn_context.activity.members_added
166
        ):
167
            return await self.on_members_added_activity(
×
168
                turn_context.activity.members_added, turn_context
169
            )
170
        if (
×
171
            turn_context.activity.members_removed is not None
172
            and turn_context.activity.members_removed
173
        ):
174
            return await self.on_members_removed_activity(
×
175
                turn_context.activity.members_removed, turn_context
176
            )
177
        return
×
178

179
    async def on_members_added_activity(
4✔
180
        self, members_added: List[ChannelAccount], turn_context: TurnContext
181
    ):  # pylint: disable=unused-argument
182
        """
183
        Override this method in a derived class to provide logic for when members other than the bot join
184
        the conversation. You can add your bot's welcome logic.
185

186
        :param members_added: A list of all the members added to the conversation, as described by the
187
        conversation update activity
188
        :type members_added: :class:`typing.List`
189
        :param turn_context: The context object for this turn
190
        :type turn_context: :class:`botbuilder.core.TurnContext`
191

192
        :returns: A task that represents the work queued to execute
193

194
        .. remarks::
195
            When the :meth:`on_conversation_update_activity()` method receives a conversation
196
            update activity that indicates
197
            one or more users other than the bot are joining the conversation, it calls this method.
198
        """
199
        return
4✔
200

201
    async def on_members_removed_activity(
4✔
202
        self, members_removed: List[ChannelAccount], turn_context: TurnContext
203
    ):  # pylint: disable=unused-argument
204
        """
205
        Override this method in a derived class to provide logic for when members other than the bot leave
206
        the conversation.  You can add your bot's good-bye logic.
207

208
        :param members_added: A list of all the members removed from the conversation, as described by the
209
        conversation update activity
210
        :type members_added: :class:`typing.List`
211
        :param turn_context: The context object for this turn
212
        :type turn_context: :class:`botbuilder.core.TurnContext`
213

214
        :returns: A task that represents the work queued to execute
215

216
        .. remarks::
217
            When the :meth:`on_conversation_update_activity()` method receives a conversation
218
            update activity that indicates one or more users other than the bot are leaving the conversation,
219
            it calls this method.
220
        """
221

222
        return
4✔
223

224
    async def on_message_reaction_activity(self, turn_context: TurnContext):
4✔
225
        """
226
        Invoked when an event activity is received from the connector when the base behavior of
227
        :meth:`on_turn()` is used.
228

229
        :param turn_context: The context object for this turn
230
        :type turn_context: :class:`botbuilder.core.TurnContext`
231

232
        :returns: A task that represents the work queued to execute
233

234
        .. remarks::
235
            Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a previously
236
            sent activity.
237

238
            Message reactions are only supported by a few channels. The activity that the message reaction corresponds
239
            to is indicated in the reply to Id property. The value of this property is the activity id of a previously
240
            sent activity given back to the bot as the response from a send call.
241
            When the :meth:`on_turn()` method receives a message reaction activity, it calls this
242
            method.
243

244
            - If the message reaction indicates that reactions were added to a message, it calls
245
            :meth:`on_reaction_added()`.
246
            - If the message reaction indicates that reactions were removed from a message, it calls
247
            :meth:`on_reaction_removed()`.
248

249
            In a derived class, override this method to add logic that applies to all message reaction activities.
250
            Add logic to apply before the reactions added or removed logic before the call to the this base class
251
            method.
252
            Add logic to apply after the reactions added or removed logic after the call to the this base class method.
253
        """
254
        if turn_context.activity.reactions_added is not None:
4✔
255
            await self.on_reactions_added(
4✔
256
                turn_context.activity.reactions_added, turn_context
257
            )
258

259
        if turn_context.activity.reactions_removed is not None:
4✔
260
            await self.on_reactions_removed(
4✔
261
                turn_context.activity.reactions_removed, turn_context
262
            )
263

264
    async def on_reactions_added(  # pylint: disable=unused-argument
4✔
265
        self, message_reactions: List[MessageReaction], turn_context: TurnContext
266
    ):
267
        """
268
        Override this method in a derived class to provide logic for when reactions to a previous activity
269
        are added to the conversation.
270

271
        :param message_reactions: The list of reactions added
272
        :type message_reactions: :class:`typing.List`
273
        :param turn_context: The context object for this turn
274
        :type turn_context: :class:`botbuilder.core.TurnContext`
275

276
        :returns: A task that represents the work queued to execute
277

278
        .. remarks::
279
            Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji)
280
            to a previously sent message on the conversation.
281
            Message reactions are supported by only a few channels.
282
            The activity that the message is in reaction to is identified by the activity's reply to ID property.
283
            The value of this property is the activity ID of a previously sent activity. When the bot sends an activity,
284
            the channel assigns an ID to it, which is available in the resource response Id of the result.
285
        """
286
        return
4✔
287

288
    async def on_reactions_removed(  # pylint: disable=unused-argument
4✔
289
        self, message_reactions: List[MessageReaction], turn_context: TurnContext
290
    ):
291
        """
292
        Override this method in a derived class to provide logic for when reactions to a previous activity
293
        are removed from the conversation.
294

295
        :param message_reactions: The list of reactions removed
296
        :type message_reactions: :class:`typing.List`
297
        :param turn_context: The context object for this turn
298
        :type turn_context: :class:`botbuilder.core.TurnContext`
299

300
        :returns: A task that represents the work queued to execute
301

302
        .. remarks::
303
            Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji)
304
            to a previously sent message on the conversation. Message reactions are supported by only a few channels.
305
            The activity that the message is in reaction to is identified by the activity's reply to Id property.
306
            The value of this property is the activity ID of a previously sent activity. When the bot sends an activity,
307
            the channel assigns an ID to it, which is available in the resource response Id of the result.
308
        """
309
        return
4✔
310

311
    async def on_event_activity(self, turn_context: TurnContext):
4✔
312
        """
313
        Invoked when an event activity is received from the connector when the base behavior of
314
        :meth:`on_turn()` is used.
315

316
        :param turn_context: The context object for this turn
317
        :type turn_context: :class:`botbuilder.core.TurnContext`
318

319
        :returns: A task that represents the work queued to execute
320

321
        .. remarks::
322
            When the :meth:`on_turn()` method receives an event activity, it calls this method.
323
            If the activity name is `tokens/response`, it calls :meth:`on_token_response_event()`;
324
            otherwise, it calls :meth:`on_event()`.
325

326
            In a derived class, override this method to add logic that applies to all event activities.
327
            Add logic to apply before the specific event-handling logic before the call to this base class method.
328
            Add logic to apply after the specific event-handling logic after the call to this base class method.
329

330
            Event activities communicate programmatic information from a client or channel to a bot.
331
            The meaning of an event activity is defined by the event activity name property, which is meaningful within
332
            the scope of a channel.
333
        """
334
        if turn_context.activity.name == SignInConstants.token_response_event_name:
×
335
            return await self.on_token_response_event(turn_context)
×
336

337
        return await self.on_event(turn_context)
×
338

339
    async def on_token_response_event(  # pylint: disable=unused-argument
4✔
340
        self, turn_context: TurnContext
341
    ):
342
        """
343
        Invoked when a `tokens/response` event is received when the base behavior of
344
        :meth:`on_event_activity()` is used.
345
        If using an `oauth_prompt`, override this method to forward this activity to the current dialog.
346

347
        :param turn_context: The context object for this turn
348
        :type turn_context: :class:`botbuilder.core.TurnContext`
349

350
        :returns: A task that represents the work queued to execute
351

352
        .. remarks::
353
            When the :meth:`on_event()` method receives an event with an activity name of
354
            `tokens/response`, it calls this method. If your bot uses an `oauth_prompt`, forward the incoming
355
            activity to the current dialog.
356
        """
357
        return
×
358

359
    async def on_event(  # pylint: disable=unused-argument
4✔
360
        self, turn_context: TurnContext
361
    ):
362
        """
363
        Invoked when an event other than `tokens/response` is received when the base behavior of
364
        :meth:`on_event_activity()` is used.
365

366

367
        :param turn_context: The context object for this turn
368
        :type turn_context: :class:`botbuilder.core.TurnContext`
369

370
        :returns: A task that represents the work queued to execute
371

372
        .. remarks::
373
            When the :meth:`on_event_activity()` is used method receives an event with an
374
            activity name other than `tokens/response`, it calls this method.
375
            This method could optionally be overridden if the bot is meant to handle miscellaneous events.
376
        """
377
        return
×
378

379
    async def on_end_of_conversation_activity(  # pylint: disable=unused-argument
4✔
380
        self, turn_context: TurnContext
381
    ):
382
        """
383
        Invoked when a conversation end activity is received from the channel.
384

385
        :param turn_context: The context object for this turn
386
        :type turn_context: :class:`botbuilder.core.TurnContext`
387
        :returns: A task that represents the work queued to execute
388
        """
389
        return
4✔
390

391
    async def on_typing_activity(  # pylint: disable=unused-argument
4✔
392
        self, turn_context: TurnContext
393
    ):
394
        """
395
        Override this in a derived class to provide logic specific to
396
        ActivityTypes.typing activities, such as the conversational logic.
397

398
        :param turn_context: The context object for this turn
399
        :type turn_context: :class:`botbuilder.core.TurnContext`
400
        :returns: A task that represents the work queued to execute
401
        """
402
        return
4✔
403

404
    async def on_installation_update(  # pylint: disable=unused-argument
4✔
405
        self, turn_context: TurnContext
406
    ):
407
        """
408
        Override this in a derived class to provide logic specific to
409
        ActivityTypes.InstallationUpdate activities.
410

411
        :param turn_context: The context object for this turn
412
        :type turn_context: :class:`botbuilder.core.TurnContext`
413
        :returns: A task that represents the work queued to execute
414
        """
415
        if turn_context.activity.action in ("add", "add-upgrade"):
4✔
416
            return await self.on_installation_update_add(turn_context)
4✔
417
        if turn_context.activity.action in ("remove", "remove-upgrade"):
4✔
418
            return await self.on_installation_update_remove(turn_context)
4✔
419
        return
4✔
420

421
    async def on_installation_update_add(  # pylint: disable=unused-argument
4✔
422
        self, turn_context: TurnContext
423
    ):
424
        """
425
        Override this in a derived class to provide logic specific to
426
        ActivityTypes.InstallationUpdate activities with 'action' set to 'add'.
427

428
        :param turn_context: The context object for this turn
429
        :type turn_context: :class:`botbuilder.core.TurnContext`
430
        :returns: A task that represents the work queued to execute
431
        """
432
        return
4✔
433

434
    async def on_installation_update_remove(  # pylint: disable=unused-argument
4✔
435
        self, turn_context: TurnContext
436
    ):
437
        """
438
        Override this in a derived class to provide logic specific to
439
        ActivityTypes.InstallationUpdate activities with 'action' set to 'remove'.
440

441
        :param turn_context: The context object for this turn
442
        :type turn_context: :class:`botbuilder.core.TurnContext`
443
        :returns: A task that represents the work queued to execute
444
        """
445
        return
4✔
446

447
    async def on_unrecognized_activity_type(  # pylint: disable=unused-argument
4✔
448
        self, turn_context: TurnContext
449
    ):
450
        """
451
        Invoked when an activity other than a message, conversation update, or event is received when the base
452
        behavior of :meth:`on_turn()` is used.
453
        If overridden, this method could potentially respond to any of the other activity types.
454

455
        :param turn_context: The context object for this turn
456
        :type turn_context: :class:`botbuilder.core.TurnContext`
457

458
        :returns: A task that represents the work queued to execute
459

460
        .. remarks::
461
            When the :meth:`on_turn()` method receives an activity that is not a message,
462
            conversation update, message reaction, or event activity, it calls this method.
463
        """
464
        return
4✔
465

466
    async def on_invoke_activity(  # pylint: disable=unused-argument
4✔
467
        self, turn_context: TurnContext
468
    ) -> Union[InvokeResponse, None]:
469
        """
470
        Registers an activity event handler for the _invoke_ event, emitted for every incoming event activity.
471

472
        :param turn_context: The context object for this turn
473
        :type turn_context: :class:`botbuilder.core.TurnContext`
474

475
        :returns: A task that represents the work queued to execute
476
        """
477
        try:
4✔
478
            if (
4✔
479
                turn_context.activity.name
480
                == SignInConstants.verify_state_operation_name
481
            ):
482
                await self.on_sign_in_invoke(turn_context)
4✔
483
                return self._create_invoke_response()
×
484

485
            # This is for back-compat with previous versions of Python SDK.  This method does not
486
            # exist in the C# SDK, and is not used in the Python SDK.
487
            if (
4✔
488
                turn_context.activity.name
489
                == SignInConstants.token_exchange_operation_name
490
            ):
NEW
491
                await self.on_teams_signin_token_exchange(turn_context)
×
NEW
492
                return self._create_invoke_response()
×
493

494
            if turn_context.activity.name == "adaptiveCard/action":
4✔
495
                invoke_value = self._get_adaptive_card_invoke_value(
×
496
                    turn_context.activity
497
                )
498
                return self._create_invoke_response(
×
499
                    await self.on_adaptive_card_invoke(turn_context, invoke_value)
500
                )
501

502
            raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED)
4✔
503
        except _InvokeResponseException as invoke_exception:
4✔
504
            return invoke_exception.create_invoke_response()
4✔
505

506
    async def on_sign_in_invoke(  # pylint: disable=unused-argument
4✔
507
        self, turn_context: TurnContext
508
    ):
509
        """
510
        Invoked when a signin/verifyState or signin/tokenExchange event is received when the base behavior of
511
        on_invoke_activity(TurnContext{InvokeActivity}) is used.
512
        If using an OAuthPrompt, override this method to forward this Activity"/ to the current dialog.
513
        By default, this method does nothing.
514

515
        :param turn_context: The context object for this turn
516
        :type turn_context: :class:`botbuilder.core.TurnContext`
517

518
        :returns: A task that represents the work queued to execute
519
        """
520
        raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED)
×
521

522
    async def on_adaptive_card_invoke(
4✔
523
        self, turn_context: TurnContext, invoke_value: AdaptiveCardInvokeValue
524
    ) -> AdaptiveCardInvokeResponse:
525
        """
526
        Invoked when the bot is sent an Adaptive Card Action Execute.
527

528
        When the on_invoke_activity method receives an Invoke with a Activity.name of `adaptiveCard/action`, it
529
        calls this method.
530

531
        :param turn_context: A context object for this turn.
532
        :type turn_context: :class:`botbuilder.core.TurnContext`
533
        :param invoke_value: A string-typed object from the incoming activity's value.
534
        :type invoke_value: :class:`botframework.schema.models.AdaptiveCardInvokeValue`
535
        :return: The HealthCheckResponse object
536
        """
537
        raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED)
×
538

539
    @staticmethod
4✔
540
    def _create_invoke_response(body: object = None) -> InvokeResponse:
4✔
541
        return InvokeResponse(status=int(HTTPStatus.OK), body=serializer_helper(body))
4✔
542

543
    def _get_adaptive_card_invoke_value(self, activity: Activity):
4✔
544
        if activity.value is None:
×
545
            response = self._create_adaptive_card_invoke_error_response(
×
546
                HTTPStatus.BAD_REQUEST, "BadRequest", "Missing value property"
547
            )
548
            raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response)
×
549

550
        invoke_value = None
×
551
        try:
×
552
            invoke_value = AdaptiveCardInvokeValue(**activity.value)
×
553
        except:
×
554
            response = self._create_adaptive_card_invoke_error_response(
×
555
                HTTPStatus.BAD_REQUEST,
556
                "BadRequest",
557
                "Value property is not properly formed",
558
            )
559
            raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response)
×
560

561
        if invoke_value.action is None:
×
562
            response = self._create_adaptive_card_invoke_error_response(
×
563
                HTTPStatus.BAD_REQUEST, "BadRequest", "Missing action property"
564
            )
565
            raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response)
×
566

567
        if invoke_value.action.get("type") != "Action.Execute":
×
568
            response = self._create_adaptive_card_invoke_error_response(
×
569
                HTTPStatus.BAD_REQUEST,
570
                "NotSupported",
571
                f"The action '{invoke_value.action.get('type')}' is not supported.",
572
            )
573
            raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response)
×
574

575
        return invoke_value
×
576

577
    def _create_adaptive_card_invoke_error_response(
4✔
578
        self, status_code: HTTPStatus, code: str, message: str
579
    ):
580
        return AdaptiveCardInvokeResponse(
×
581
            status_code=status_code,
582
            type="application/vnd.microsoft.error",
583
            value=Exception(code, message),
584
        )
585

586

587
class _InvokeResponseException(Exception):
4✔
588
    def __init__(self, status_code: HTTPStatus, body: object = None):
4✔
589
        super(_InvokeResponseException, self).__init__()
4✔
590
        self._status_code = status_code
4✔
591
        self._body = body
4✔
592

593
    def create_invoke_response(self) -> InvokeResponse:
4✔
594
        return InvokeResponse(status=int(self._status_code), body=self._body)
4✔
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