Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 73 additions & 55 deletions telegram/ext/conversationhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ class _ConversationTimeoutContext:
__slots__ = ('conversation_key', 'update', 'dispatcher', 'callback_context')

def __init__(
self,
conversation_key: Tuple[int, ...],
update: Update,
dispatcher: 'Dispatcher',
callback_context: Optional[CallbackContext],
self,
conversation_key: Tuple[int, ...],
update: Update,
dispatcher: 'Dispatcher',
callback_context: Optional[CallbackContext],
):
self.conversation_key = conversation_key
self.update = update
Expand Down Expand Up @@ -126,6 +126,13 @@ class ConversationHandler(Handler[Update, CCT]):
trigger the start of the conversation. The first handler which :attr:`check_update`
method returns :obj:`True` will be used. If all return :obj:`False`, the update is not
handled.
prefallbacks (List[:class:`telegram.ext.Handler`]): A list of handlers that will be trigger
before all the other "states" and "fallbacks" handlers. Useful for such commands as
"cancel", "back", "repeat". Intended to place part of "fallbacks" handlers inside and
avoid using many "& ~" conditions in "Filters".
:obj:`False` on :attr:`check_update`. The first handler which :attr:`check_update`
method returns :obj:`True` will be used. If all return :obj:`False`, the update will be
forwarded to "states" handlers.
states (Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]): A :obj:`dict` that
defines the different states of conversation a user can be in and one or more
associated ``Handler`` objects that should be used in that state. The first handler
Expand Down Expand Up @@ -213,25 +220,28 @@ class ConversationHandler(Handler[Update, CCT]):
WAITING: ClassVar[int] = -3
""":obj:`int`: Used as a constant to handle state when a conversation is still waiting on the
previous ``@run_sync`` decorated running handler to finish."""

# pylint: disable=W0231
def __init__(
self,
entry_points: List[Handler[Update, CCT]],
states: Dict[object, List[Handler[Update, CCT]]],
fallbacks: List[Handler[Update, CCT]],
allow_reentry: bool = False,
per_chat: bool = True,
per_user: bool = True,
per_message: bool = False,
conversation_timeout: Union[float, datetime.timedelta] = None,
name: str = None,
persistent: bool = False,
map_to_parent: Dict[object, object] = None,
run_async: bool = False,
self,
entry_points: List[Handler[Update, CCT]],
states: Dict[object, List[Handler[Update, CCT]]],
fallbacks: List[Handler[Update, CCT]],
prefallbacks: List[Handler[Update, CCT]] = None,
allow_reentry: bool = False,
per_chat: bool = True,
per_user: bool = True,
per_message: bool = False,
conversation_timeout: Union[float, datetime.timedelta] = None,
name: str = None,
persistent: bool = False,
map_to_parent: Dict[object, object] = None,
run_async: bool = False,
):
self.run_async = run_async

self._entry_points = entry_points
self._prefallbacks = prefallbacks or []
self._states = states
self._fallbacks = fallbacks

Expand Down Expand Up @@ -266,6 +276,7 @@ def __init__(
)

all_handlers: List[Handler] = []
all_handlers.extend(prefallbacks) # type: ignore[arg-type]
all_handlers.extend(entry_points)
all_handlers.extend(fallbacks)

Expand Down Expand Up @@ -324,6 +335,18 @@ def entry_points(self) -> List[Handler]:
def entry_points(self, value: object) -> NoReturn:
raise ValueError('You can not assign a new value to entry_points after initialization.')

@property
def prefallbacks(self) -> List[Handler]:
"""List[:class:`telegram.ext.Handler`]: A list of handlers that will be
checked for handling before all the other handlers
:obj:`False` on :attr:`check_update`.
"""
return self._prefallbacks # pylint: disable=maybe-no-member

@prefallbacks.setter
def prefallbacks(self, value: object) -> NoReturn:
raise ValueError('You can not assign a new value to prefallbacks after initialization.')

@property
def states(self) -> Dict[object, List[Handler]]:
"""Dict[:obj:`object`, List[:class:`telegram.ext.Handler`]]: A :obj:`dict` that
Expand Down Expand Up @@ -386,7 +409,7 @@ def per_message(self, value: object) -> NoReturn:

@property
def conversation_timeout(
self,
self,
) -> Optional[Union[float, datetime.timedelta]]:
""":obj:`float` | :obj:`datetime.timedelta`: Optional. When this
handler is inactive more than this timeout (in seconds), it will be automatically
Expand Down Expand Up @@ -483,12 +506,12 @@ def _resolve_promise(self, state: Tuple) -> object:
return res

def _schedule_job(
self,
new_state: object,
dispatcher: 'Dispatcher',
update: Update,
context: Optional[CallbackContext],
conversation_key: Tuple[int, ...],
self,
new_state: object,
dispatcher: 'Dispatcher',
update: Update,
context: Optional[CallbackContext],
conversation_key: Tuple[int, ...],
) -> None:
if new_state != self.END:
try:
Expand Down Expand Up @@ -565,41 +588,36 @@ def check_update(self, update: object) -> CheckUpdateType: # pylint: disable=R0
check = entry_point.check_update(update)
if check is not None and check is not False:
handler = entry_point
break

else:
if state is None:
return None
return key, handler, check
# Find a prefallback handler before all others handlers
for prefallback in self.prefallbacks:
check = prefallback.check_update(update)
if check is not None and check is not False:
handler = prefallback
return key, handler, check

# Get the handler list for current state, if we didn't find one yet and we're still here
if state is not None and not handler:
handlers = self.states.get(state)

for candidate in handlers or []:
check = candidate.check_update(update)
if check is not None and check is not False:
handler = candidate
break

# Find a fallback handler if all other handlers fail
else:
for fallback in self.fallbacks:
check = fallback.check_update(update)
if check is not None and check is not False:
handler = fallback
break

else:
return None
handlers = self.states.get(state)
for candidate in handlers or []:
check = candidate.check_update(update)
if check is not None and check is not False:
handler = candidate
return key, handler, check

return key, handler, check # type: ignore[return-value]
# Find a fallback handler if all other handlers fail
for fallback in self.fallbacks:
check = fallback.check_update(update)
if check is not None and check is not False:
handler = fallback
return key, handler, check
return None

def handle_update( # type: ignore[override]
self,
update: Update,
dispatcher: 'Dispatcher',
check_result: CheckUpdateType,
context: CallbackContext = None,
self,
update: Update,
dispatcher: 'Dispatcher',
check_result: CheckUpdateType,
context: CallbackContext = None,
) -> Optional[object]:
"""Send the update to the callback for the current state and Handler

Expand Down